Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ docs/parts/examples/*.md
Gemfile.lock
gemfiles/*.lock
test/dummy/config/master.key
endor/bundle
2 changes: 2 additions & 0 deletions app/assets/config/active_prompt_manifest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
//= link active_prompt/application.js
//= link active_prompt/application.css
8 changes: 8 additions & 0 deletions app/assets/javascripts/active_prompt/application.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// This file is compiled into the host app's asset pipeline as active_prompt/application.js
// Add engine-specific JS here.
//= require_self

(function () {
// Namespace guard
window.ActivePrompt = window.ActivePrompt || {};
})();
6 changes: 6 additions & 0 deletions app/assets/stylesheets/active_prompt/application.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
*= require_self
*/

/* Add engine-specific styles here */
.active-prompt--hidden { display: none; }
7 changes: 7 additions & 0 deletions app/controllers/active_prompt/application_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

module ActivePrompt
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
end
end
9 changes: 9 additions & 0 deletions app/controllers/active_prompt/health_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

module ActivePrompt
class HealthController < ApplicationController
def show
render plain: "ok"
end
end
end
6 changes: 6 additions & 0 deletions app/helpers/active_prompt/application_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

module ActivePrompt
module ApplicationHelper
end
end
10 changes: 10 additions & 0 deletions app/models/active_prompt/action.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true
module ActivePrompt
class Action < ApplicationRecord
self.table_name = "active_prompt_actions"

belongs_to :prompt, class_name: "ActivePrompt::Prompt", inverse_of: :actions

validates :name, presence: true
end
end
7 changes: 7 additions & 0 deletions app/models/active_prompt/application_record.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

module ActivePrompt
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
end
13 changes: 13 additions & 0 deletions app/models/active_prompt/context.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true
module ActivePrompt
# Polymorphic join: attach prompts to any agent record
class Context < ApplicationRecord
self.table_name = "active_prompt_contexts"

belongs_to :agent, polymorphic: true, inverse_of: :prompt_contexts
belongs_to :prompt, class_name: "ActivePrompt::Prompt", inverse_of: :contexts

validates :agent, :prompt, presence: true
validates :label, length: { maximum: 255 }, allow_nil: true
end
end
11 changes: 11 additions & 0 deletions app/models/active_prompt/message.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true
module ActivePrompt
class Message < ApplicationRecord
self.table_name = "active_prompt_messages"

belongs_to :prompt, class_name: "ActivePrompt::Prompt", inverse_of: :messages

enum :role, %i[system user assistant tool], prefix: true
validates :role, :content, presence: true
end
end
27 changes: 27 additions & 0 deletions app/models/active_prompt/prompt.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true
module ActivePrompt
class Prompt < ApplicationRecord
self.table_name = "active_prompt_prompts"

has_many :messages, class_name: "ActivePrompt::Message", dependent: :destroy, inverse_of: :prompt
has_many :actions, class_name: "ActivePrompt::Action", dependent: :destroy, inverse_of: :prompt

has_many :contexts, class_name: "ActivePrompt::Context", dependent: :destroy, inverse_of: :prompt
has_many :agents, through: :contexts, source: :agent

validates :name, presence: true

scope :with_runtime_associations, -> { includes(:messages, :actions) }

def to_runtime
{
name: name,
description: description,
template: template,
messages: messages.order(:position).as_json,
actions: actions.as_json,
metadata: metadata || {}
}
end
end
end
5 changes: 5 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

ActivePrompt::Engine.routes.draw do
get "health", to: "health#show", as: :health
end
44 changes: 44 additions & 0 deletions db/migrate/20251127000000_create_active_prompt_core.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true
class CreateActivePromptCore < ActiveRecord::Migration[7.0]
def change
create_table :active_prompt_prompts do |t|
t.string :name, null: false
t.text :description
t.json :metadata, null: false, default: {}
t.text :template
t.timestamps
end
add_index :active_prompt_prompts, :name

create_table :active_prompt_messages do |t|
t.references :prompt, null: false, foreign_key: { to_table: :active_prompt_prompts }
t.string :role, null: false
t.text :content, null: false
t.integer :position
t.json :metadata, null: false, default: {}
t.timestamps
end
add_index :active_prompt_messages, [:prompt_id, :position], name: "idx_ap_messages_prompt_position"

create_table :active_prompt_actions do |t|
t.references :prompt, null: false, foreign_key: { to_table: :active_prompt_prompts }
t.string :name, null: false
t.string :tool_name
t.json :parameters, null: false, default: {}
t.json :result, null: false, default: {}
t.string :status
t.timestamps
end
add_index :active_prompt_actions, [:prompt_id, :name], name: "idx_ap_actions_prompt_name"

create_table :active_prompt_contexts do |t|
t.string :agent_type, null: false
t.bigint :agent_id, null: false
t.references :prompt, null: false, foreign_key: { to_table: :active_prompt_prompts }
t.string :label
t.json :metadata, null: false, default: {}
t.timestamps
end
add_index :active_prompt_contexts, [:agent_type, :agent_id, :prompt_id], unique: true, name: "idx_ap_contexts_agent_prompt"
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!-- Generated from structured_output_json_parsing_test.rb:69 -->
[activeagent/test/integration/structured_output_json_parsing_test.rb:69](vscode://file//Users/sarbadajaiswal/development/Justin/activeagents/activeagent/test/integration/structured_output_json_parsing_test.rb:69)
<!-- Test: test-structured-output-sets-content-type-to-application/json-and-auto-parses-JSON -->

```ruby
# Response object
#<ActiveAgent::GenerationProvider::Response:0x20b0
@message=#<ActiveAgent::ActionPrompt::Message:0x20b8
@action_id=nil,
@action_name=nil,
@action_requested=false,
@charset="UTF-8",
@content={"name" => "John Doe", "age" => 30, "email" => "john@example.com"},
@role=:assistant>
@prompt=#<ActiveAgent::ActionPrompt::Prompt:0x20c0 ...>
@content_type="application/json"
@raw_response={...}>

# Message content
response.message.content # => {"name" => "John Doe", "age" => 30, "email" => "john@example.com"}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!-- Generated from structured_output_json_parsing_test.rb:151 -->
[activeagent/test/integration/structured_output_json_parsing_test.rb:151](vscode://file//Users/sarbadajaiswal/development/Justin/activeagents/activeagent/test/integration/structured_output_json_parsing_test.rb:151)
<!-- Test: test-without-structured-output-uses-text/plain-content-type -->

```ruby
# Response object
#<ActiveAgent::GenerationProvider::Response:0x20d0
@message=#<ActiveAgent::ActionPrompt::Message:0x20d8
@action_id=nil,
@action_name=nil,
@action_requested=false,
@charset="UTF-8",
@content="The capital of France is Paris.",
@role=:assistant>
@prompt=#<ActiveAgent::ActionPrompt::Prompt:0x20e0 ...>
@content_type="text/plain"
@raw_response={...}>

# Message content
response.message.content # => "The capital of France is Paris."
```
34 changes: 34 additions & 0 deletions lib/active_agent/has_context.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true
module ActiveAgent
module HasContext
extend ActiveSupport::Concern

class_methods do
# Example:
# has_context prompts: :prompts, messages: :messages, tools: :actions
#
# Associations added:
# has_many :prompt_contexts (ActivePrompt::Context, as: :agent)
# has_many :prompts, :messages, :actions (through prompt_contexts/prompts)
def has_context(prompts: :prompts, messages: :messages, tools: :actions)
has_many :prompt_contexts,
class_name: "ActivePrompt::Context",
as: :agent,
dependent: :destroy,
inverse_of: :agent

has_many prompts, through: :prompt_contexts, source: :prompt
has_many messages, through: prompts, source: :messages
has_many tools, through: prompts, source: :actions

define_method :add_prompt do |prompt, label: nil, metadata: {}|
ActivePrompt::Context.create!(agent: self, prompt:, label:, metadata:)
end

define_method :remove_prompt do |prompt|
prompt_contexts.where(prompt:).destroy_all
end
end
end
end
end
11 changes: 8 additions & 3 deletions lib/active_agent/providers/common/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -352,10 +352,15 @@ def <=>(other)
# model1 = Message.new(content: "Hello")
# model2 = Message.new(content: "Hello")
# model1 == model2 #=> true
def ==(other)
serialize == other&.serialize
end
def ==(other)
serialize == other&.serialize
end
end

# Zeitwerk expects this file to define ActiveAgent::Providers::Common::Model
# based on its path. Provide an alias so autoloading succeeds while keeping
# the BaseModel name used throughout the codebase.
Model = BaseModel
end
end
end
7 changes: 7 additions & 0 deletions lib/active_agent/providers/openrouter_provider.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
# OpenRouter, just copying OpenAI
require_relative "open_router_provider"

# Zeitwerk expects OpenrouterProvider from this file name.
module ActiveAgent
module Providers
OpenrouterProvider = OpenRouterProvider
end
end
12 changes: 12 additions & 0 deletions lib/active_agent/test_case.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

require "active_support/test_case"

module ActiveAgent
class TestCase < ActiveSupport::TestCase
# minimal base to satisfy Zeitwerk
end
end

# Back-compat for any existing tests
ActiveAgentTestCase = ActiveAgent::TestCase unless defined?(ActiveAgentTestCase)
7 changes: 7 additions & 0 deletions lib/active_prompt.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

require "active_prompt/version"
require "active_prompt/engine" if defined?(Rails)

module ActivePrompt
end
36 changes: 36 additions & 0 deletions lib/active_prompt/engine.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

require "rails/engine"

module ActivePrompt
class Engine < ::Rails::Engine
isolate_namespace ActivePrompt

# Ensures the engine's app/ is eager loaded in production and autoloaded in dev/test
config.autoload_paths << root.join("lib").to_s

# Keep generated files tidy (no assets/helpers/tests by default from generators)
config.generators do |g|
g.assets false
g.helper false
g.test_framework :rspec, fixture: false if defined?(RSpec)
end

# Sprockets / asset pipeline configuration
initializer "active_prompt.assets.precompile" do |app|
# When the engine is used within a host Rails app, ensure our assets are precompiled
if app.config.respond_to?(:assets)
app.config.assets.paths << root.join("app", "assets")
app.config.assets.precompile += %w[
active_prompt/application.js
active_prompt/application.css
]
end
end

# Make sure the engine’s translations are available
initializer "active_prompt.i18n" do
config.i18n.load_path += Dir[root.join("config", "locales", "**", "*.yml")]
end
end
end
5 changes: 5 additions & 0 deletions lib/active_prompt/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

module ActivePrompt
VERSION = "0.1.0"
end
19 changes: 19 additions & 0 deletions lib/generators/active_prompt/install/install_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true
require "rails/generators"
module ActivePrompt
module Generators
class InstallGenerator < Rails::Generators::Base
source_root File.expand_path("../../../..", __dir__) # engine root

desc "Copy ActivePrompt migrations into the host app"

def copy_migrations
rake("railties:install:migrations FROM=active_prompt")
end

def show_readme
say_status :info, "Run `bin/rails db:migrate` to apply ActivePrompt tables.", :blue
end
end
end
end
22 changes: 22 additions & 0 deletions test/active_prompt/asset_pipeline_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true
require "test_helper"

class ActivePromptAssetPipelineTest < ActiveSupport::TestCase
def assets_enabled?
Rails.application.config.respond_to?(:assets) && Rails.application.config.assets
end

test "adds engine assets path to host app (if assets enabled)" do
skip "Assets not enabled in host app" unless assets_enabled?
paths = Rails.application.config.assets.paths.map(&:to_s)
expected = ActivePrompt::Engine.root.join("app", "assets").to_s
assert_includes paths, expected
end

test "adds engine assets to precompile list (if assets enabled)" do
skip "Assets not enabled in host app" unless assets_enabled?
precompile = Array(Rails.application.config.assets.precompile).map(&:to_s)
assert_includes precompile, "active_prompt/application.js"
assert_includes precompile, "active_prompt/application.css"
end
end
Loading