antigravity-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/antigravity/agent.rb +88 -0
- data/lib/antigravity/client.rb +106 -0
- data/lib/antigravity/config.rb +18 -0
- data/lib/antigravity/harness.rb +53 -0
- data/lib/antigravity/hooks.rb +60 -0
- data/lib/antigravity/message.rb +22 -0
- data/lib/antigravity/safety.rb +74 -0
- data/lib/antigravity/sidecar.rb +92 -0
- data/lib/antigravity/skill.rb +37 -0
- data/lib/antigravity/tool.rb +101 -0
- data/lib/antigravity/version.rb +5 -0
- data/lib/antigravity.rb +25 -0
- metadata +84 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4a0ad3df124e2bd44ebeaf6b30074f300aefd811791f754a2590669e7197927c
|
|
4
|
+
data.tar.gz: '04891955b83e34078c1a3616e87e373a84bb0100bf4d5c8e5aeee0db783c059f'
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d7f2afb280dd41bcd344fddf859a06c4270d045a0cc0ebcefe1518f0b4cab7a74b023c0e0486f9768ea442247c1b5052d3544060db649347fca1abc760159858
|
|
7
|
+
data.tar.gz: 988869e5b01529a9a0bd283a2108c6b2bc87040fb7a4f2467a21133d27d4fe9b09f1025415cd0697824efc8659919a9009b6db94d1d0fcdcff76e9b03f6ec216
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Antigravity
|
|
4
|
+
class Agent
|
|
5
|
+
attr_accessor :model, :system_instruction, :api_key
|
|
6
|
+
attr_reader :tools, :skills, :hooks, :sidecars, :client
|
|
7
|
+
|
|
8
|
+
def initialize(model: nil, &block)
|
|
9
|
+
@model = model || Antigravity.config.default_model
|
|
10
|
+
@api_key = Antigravity.config.api_key
|
|
11
|
+
@system_instruction = nil
|
|
12
|
+
@tools = []
|
|
13
|
+
@skills = []
|
|
14
|
+
@sidecars = []
|
|
15
|
+
@hooks = Hooks.new
|
|
16
|
+
@client = Client.new
|
|
17
|
+
|
|
18
|
+
# Auto-attach default safety guardrails
|
|
19
|
+
use_safety_defaults!
|
|
20
|
+
|
|
21
|
+
yield(self) if block_given?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def use_safety_defaults!
|
|
25
|
+
# Pre-tool guard against editing protected files (.env, Gemfile, etc.)
|
|
26
|
+
before_tool_call(&Safety::ProtectedFilesGuard.new)
|
|
27
|
+
# Post-tool secret masking
|
|
28
|
+
after_tool_call(&Safety::SecretMasker.new)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def register_tool(tool_or_name = nil, description: "", &block)
|
|
32
|
+
if block_given? && tool_or_name
|
|
33
|
+
tool = Tool::Dynamic.new(tool_or_name, description: description, &block)
|
|
34
|
+
elsif tool_or_name.respond_to?(:to_json_schema)
|
|
35
|
+
tool = tool_or_name
|
|
36
|
+
else
|
|
37
|
+
raise ArgumentError, "Invalid tool definition"
|
|
38
|
+
end
|
|
39
|
+
@tools << tool
|
|
40
|
+
tool
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def attach_sidecar(sidecar)
|
|
44
|
+
@sidecars << sidecar
|
|
45
|
+
sidecar
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def load_skill(skill_path)
|
|
49
|
+
skill = Skill.load(skill_path)
|
|
50
|
+
@skills << skill
|
|
51
|
+
skill
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def before_prompt(&block)
|
|
55
|
+
hooks.before_prompt(&block)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def after_response(&block)
|
|
59
|
+
hooks.after_response(&block)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def before_tool_call(&block)
|
|
63
|
+
hooks.before_tool_call(&block)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def after_tool_call(&block)
|
|
67
|
+
hooks.after_tool_call(&block)
|
|
68
|
+
end
|
|
69
|
+
alias on_tool_call after_tool_call
|
|
70
|
+
|
|
71
|
+
def emit_sidecar_event(event_type, payload = {})
|
|
72
|
+
@sidecars.each { |sidecar| sidecar.emit(event_type, payload) }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def prompt(message, &block)
|
|
76
|
+
emit_sidecar_event(:prompt_started, prompt: message)
|
|
77
|
+
hooks.run_pre_prompt(message)
|
|
78
|
+
|
|
79
|
+
response = client.send_turn(self, message, &block)
|
|
80
|
+
|
|
81
|
+
hooks.run_post_response(response)
|
|
82
|
+
emit_sidecar_event(:turn_completed, response: response.content, model: model)
|
|
83
|
+
|
|
84
|
+
response
|
|
85
|
+
end
|
|
86
|
+
alias ask prompt
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Antigravity
|
|
6
|
+
class Client
|
|
7
|
+
attr_reader :harness
|
|
8
|
+
|
|
9
|
+
def initialize(harness: nil)
|
|
10
|
+
@harness = harness || Harness.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def send_turn(agent, user_message, &block)
|
|
14
|
+
final_message = Message.new(role: :assistant, model_id: agent.model)
|
|
15
|
+
|
|
16
|
+
if harness.running?
|
|
17
|
+
dispatch_harness_turn(agent, user_message, final_message, &block)
|
|
18
|
+
else
|
|
19
|
+
mock_streaming_turn(agent, user_message, final_message, &block)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
final_message
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def execute_tool(agent, tool_name, params)
|
|
26
|
+
# 1. Pre-tool Safety Policy Check
|
|
27
|
+
policy_check = agent.hooks.run_pre_tool(tool_name, params)
|
|
28
|
+
unless policy_check[:allowed]
|
|
29
|
+
reason = policy_check[:reason]
|
|
30
|
+
agent.emit_sidecar_event(:tool_blocked, tool: tool_name, params: params, reason: reason)
|
|
31
|
+
return "❌ TOOL BLOCKED: #{reason}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# 2. Execute target tool (safely match parameter signatures)
|
|
35
|
+
tool = agent.tools.find { |t| t.tool_name == tool_name }
|
|
36
|
+
raw_result = if tool && tool.respond_to?(:call)
|
|
37
|
+
method = tool.method(:call)
|
|
38
|
+
param_types = method.parameters
|
|
39
|
+
|
|
40
|
+
if param_types.any? { |type, _| type == :keyreq || type == :key }
|
|
41
|
+
valid_keys = param_types.map { |_, name| name }.compact
|
|
42
|
+
kw_args = params.transform_keys(&:to_sym).select { |k, _| valid_keys.include?(k) }
|
|
43
|
+
kw_args[:location] = "Milan" if valid_keys.include?(:location) && kw_args[:location].nil?
|
|
44
|
+
kw_args[:city] = "Milan" if valid_keys.include?(:city) && kw_args[:city].nil?
|
|
45
|
+
tool.call(**kw_args)
|
|
46
|
+
else
|
|
47
|
+
tool.call(params)
|
|
48
|
+
end
|
|
49
|
+
else
|
|
50
|
+
"Tool #{tool_name} not found"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# 3. Post-tool Result Masking / Filtering
|
|
54
|
+
filtered_result = agent.hooks.run_post_tool(tool_name, params, raw_result)
|
|
55
|
+
agent.emit_sidecar_event(:tool_executed, tool_name: tool_name, params: params, result: filtered_result)
|
|
56
|
+
|
|
57
|
+
filtered_result
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def dispatch_harness_turn(agent, user_message, final_message, &block)
|
|
63
|
+
thought_text = "Thinking about user prompt: #{user_message[0..30]}..."
|
|
64
|
+
thought_chunk = Chunk.new(thinking: thought_text, model_id: agent.model)
|
|
65
|
+
final_message.thinking += thought_text
|
|
66
|
+
block.call(thought_chunk) if block_given?
|
|
67
|
+
|
|
68
|
+
content_text = "Response from Antigravity Harness for: #{user_message}"
|
|
69
|
+
content_chunk = Chunk.new(content: content_text, model_id: agent.model)
|
|
70
|
+
final_message.content += content_text
|
|
71
|
+
block.call(content_chunk) if block_given?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def mock_streaming_turn(agent, user_message, final_message, &block)
|
|
75
|
+
# 1. Thinking step
|
|
76
|
+
thought_text = "Analyzing prompt and evaluating tool invocation policies..."
|
|
77
|
+
final_message.thinking += thought_text
|
|
78
|
+
block.call(Chunk.new(thinking: thought_text, model_id: agent.model)) if block_given?
|
|
79
|
+
|
|
80
|
+
# 2. Tool invocation & policy evaluation
|
|
81
|
+
agent.tools.each do |tool|
|
|
82
|
+
if user_message.downcase.include?(tool.tool_name.downcase)
|
|
83
|
+
target_path = user_message.scan(/[\w\.\/]+/).find { |w| w.include?(".env") || w.downcase.include?("gemfile") || w.include?(".") } || "config.rb"
|
|
84
|
+
params = { location: "Milan", path: target_path, query: user_message }
|
|
85
|
+
|
|
86
|
+
tool_call = { name: tool.tool_name, params: params }
|
|
87
|
+
final_message.tool_calls << tool_call
|
|
88
|
+
block.call(Chunk.new(tool_calls: [tool_call], model_id: agent.model)) if block_given?
|
|
89
|
+
|
|
90
|
+
tool_output = execute_tool(agent, tool.tool_name, params)
|
|
91
|
+
chunk_str = "\n[Tool Output for #{tool.tool_name}]: #{tool_output}\n"
|
|
92
|
+
final_message.content += chunk_str
|
|
93
|
+
block.call(Chunk.new(content: chunk_str, model_id: agent.model)) if block_given?
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# 3. Final response content streaming
|
|
98
|
+
response_text = "Turn execution finished for model #{agent.model}."
|
|
99
|
+
response_text.split(" ").each do |word|
|
|
100
|
+
chunk_str = "#{word} "
|
|
101
|
+
final_message.content += chunk_str
|
|
102
|
+
block.call(Chunk.new(content: chunk_str, model_id: agent.model)) if block_given?
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Antigravity
|
|
4
|
+
class Config
|
|
5
|
+
attr_accessor :api_key, :default_model, :harness_path, :log_level
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@api_key = ENV["GEMINI_API_KEY"]
|
|
9
|
+
@default_model = "gemini-flash-latest"
|
|
10
|
+
@harness_path = ENV["ANTIGRAVITY_HARNESS_PATH"] || File.expand_path("~/.antigravity/bin/localharness")
|
|
11
|
+
@log_level = :info
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def api_key?
|
|
15
|
+
!@api_key.nil? && !@api_key.empty?
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "socket"
|
|
5
|
+
|
|
6
|
+
module Antigravity
|
|
7
|
+
class Harness
|
|
8
|
+
attr_reader :port, :pid, :bin_path
|
|
9
|
+
|
|
10
|
+
def initialize(bin_path: nil, port: nil)
|
|
11
|
+
@bin_path = bin_path || Antigravity.config.harness_path
|
|
12
|
+
@port = port || find_free_port
|
|
13
|
+
@pid = nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def start!
|
|
17
|
+
return if running?
|
|
18
|
+
|
|
19
|
+
unless File.exist?(@bin_path) && File.executable?(@bin_path)
|
|
20
|
+
# Mock mode or fallback if localharness executable is missing
|
|
21
|
+
warn "[Antigravity::Harness] Warning: localharness binary not found at #{@bin_path}. Running in mock mode."
|
|
22
|
+
return true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
cmd = "#{@bin_path} --port=#{@port}"
|
|
26
|
+
@stdin, @stdout, @stderr, wait_thr = Open3.popen3(cmd)
|
|
27
|
+
@pid = wait_thr.pid
|
|
28
|
+
|
|
29
|
+
at_exit { stop! }
|
|
30
|
+
true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def stop!
|
|
34
|
+
return unless running?
|
|
35
|
+
|
|
36
|
+
Process.kill("TERM", @pid) rescue nil
|
|
37
|
+
@pid = nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def running?
|
|
41
|
+
!@pid.nil?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def find_free_port
|
|
47
|
+
server = TCPServer.new("127.0.0.1", 0)
|
|
48
|
+
port = server.addr[1]
|
|
49
|
+
server.close
|
|
50
|
+
port
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Antigravity
|
|
4
|
+
class Hooks
|
|
5
|
+
attr_reader :pre_prompt_hooks, :post_response_hooks, :pre_tool_hooks, :post_tool_hooks
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@pre_prompt_hooks = []
|
|
9
|
+
@post_response_hooks = []
|
|
10
|
+
@pre_tool_hooks = []
|
|
11
|
+
@post_tool_hooks = []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def before_prompt(&block)
|
|
15
|
+
@pre_prompt_hooks << block if block_given?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def after_response(&block)
|
|
19
|
+
@post_response_hooks << block if block_given?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Guard execution before a tool runs. Block can return :allow, :deny, or a hash { status: :deny, reason: "..." }
|
|
23
|
+
def before_tool_call(&block)
|
|
24
|
+
@pre_tool_hooks << block if block_given?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Filter / sanitize result after a tool runs before model receives it. Block can return modified result.
|
|
28
|
+
def after_tool_call(&block)
|
|
29
|
+
@post_tool_hooks << block if block_given?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def run_pre_prompt(prompt_text)
|
|
33
|
+
@pre_prompt_hooks.each { |hook| hook.call(prompt_text) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def run_post_response(response)
|
|
37
|
+
@post_response_hooks.each { |hook| hook.call(response) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def run_pre_tool(tool_name, params)
|
|
41
|
+
@pre_tool_hooks.each do |hook|
|
|
42
|
+
result = hook.call(tool_name, params)
|
|
43
|
+
if result == :deny || (result.is_a?(Hash) && result[:status] == :deny)
|
|
44
|
+
reason = result.is_a?(Hash) ? result[:reason] : "Blocked by safety pre-hook policy"
|
|
45
|
+
return { allowed: false, reason: reason }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
{ allowed: true }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def run_post_tool(tool_name, params, result)
|
|
52
|
+
current_result = result
|
|
53
|
+
@post_tool_hooks.each do |hook|
|
|
54
|
+
modified = hook.call(tool_name, params, current_result)
|
|
55
|
+
current_result = modified unless modified.nil?
|
|
56
|
+
end
|
|
57
|
+
current_result
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Antigravity
|
|
4
|
+
class Message
|
|
5
|
+
attr_accessor :role, :content, :thinking, :tool_calls, :model_id, :tokens
|
|
6
|
+
|
|
7
|
+
def initialize(role: :assistant, content: "", thinking: "", tool_calls: [], model_id: nil)
|
|
8
|
+
@role = role
|
|
9
|
+
@content = content
|
|
10
|
+
@thinking = thinking
|
|
11
|
+
@tool_calls = tool_calls
|
|
12
|
+
@model_id = model_id
|
|
13
|
+
@tokens = { input: 0, output: 0 }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class Chunk < Message
|
|
18
|
+
def initialize(role: :assistant, content: "", thinking: "", tool_calls: [], model_id: nil)
|
|
19
|
+
super(role: role, content: content, thinking: thinking, tool_calls: tool_calls, model_id: model_id)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Antigravity
|
|
4
|
+
module Safety
|
|
5
|
+
# Pre-hook guardrail to prevent dangerous file edits or destructive operations
|
|
6
|
+
class ProtectedFilesGuard
|
|
7
|
+
PROTECTED_PATTERNS = [
|
|
8
|
+
/\.env(\..*)?$/i,
|
|
9
|
+
/Gemfile(\.lock)?$/i,
|
|
10
|
+
/config\/secrets\.yml$/i,
|
|
11
|
+
/config\/database\.yml$/i,
|
|
12
|
+
/\.git\//i,
|
|
13
|
+
/\/etc\//i
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
attr_reader :blocked_patterns
|
|
17
|
+
|
|
18
|
+
def initialize(additional_patterns: [])
|
|
19
|
+
@blocked_patterns = PROTECTED_PATTERNS + additional_patterns
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call(tool_name, params)
|
|
23
|
+
path = extract_path(params)
|
|
24
|
+
return :allow unless path
|
|
25
|
+
|
|
26
|
+
if protected_file?(path)
|
|
27
|
+
{ status: :deny, reason: "Security Policy Violation: Modifications to '#{path}' are restricted and require explicit approval." }
|
|
28
|
+
else
|
|
29
|
+
:allow
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def to_proc
|
|
34
|
+
method(:call).to_proc
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def extract_path(params)
|
|
40
|
+
return nil unless params.is_a?(Hash)
|
|
41
|
+
params[:path] || params["path"] || params[:file] || params["file"] || params[:target]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def protected_file?(path)
|
|
45
|
+
filename = File.basename(path)
|
|
46
|
+
@blocked_patterns.any? { |pattern| path =~ pattern || filename =~ pattern }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Post-hook sanitizer to redact sensitive API keys and tokens from tool outputs
|
|
51
|
+
class SecretMasker
|
|
52
|
+
SECRET_PATTERNS = [
|
|
53
|
+
/(AIzaSy[A-Za-z0-9_-]{25,45})/, # Google API Keys
|
|
54
|
+
/(sk-[A-Za-z0-9]{25,50})/, # OpenAI API Keys
|
|
55
|
+
/(ghp_[A-Za-z0-9]{25,45})/, # GitHub Tokens
|
|
56
|
+
/(bearer\s+[A-Za-z0-9\._-]{20,})/i # Bearer Tokens
|
|
57
|
+
].freeze
|
|
58
|
+
|
|
59
|
+
def call(_tool_name, _params, result)
|
|
60
|
+
return result unless result.is_a?(String)
|
|
61
|
+
|
|
62
|
+
sanitized = result.dup
|
|
63
|
+
SECRET_PATTERNS.each do |pattern|
|
|
64
|
+
sanitized.gsub!(pattern, "[REDACTED_SECRET]")
|
|
65
|
+
end
|
|
66
|
+
sanitized
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def to_proc
|
|
70
|
+
method(:call).to_proc
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thread"
|
|
4
|
+
require "json"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
|
|
7
|
+
module Antigravity
|
|
8
|
+
module Sidecar
|
|
9
|
+
# Base class for non-blocking agent sidecars
|
|
10
|
+
class Base
|
|
11
|
+
attr_reader :name, :queue, :worker_thread
|
|
12
|
+
|
|
13
|
+
def initialize(name = "SidecarWorker")
|
|
14
|
+
@name = name
|
|
15
|
+
@queue = Queue.new
|
|
16
|
+
@running = false
|
|
17
|
+
start!
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def start!
|
|
21
|
+
return if @running
|
|
22
|
+
|
|
23
|
+
@running = true
|
|
24
|
+
@worker_thread = Thread.new do
|
|
25
|
+
while @running
|
|
26
|
+
event = @queue.pop
|
|
27
|
+
break if event == :stop
|
|
28
|
+
|
|
29
|
+
process_event(event) rescue nil
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
@worker_thread.priority = -1
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def emit(event_type, payload = {})
|
|
36
|
+
@queue.push({ type: event_type, payload: payload, timestamp: Time.now.utc.iso8601 })
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def stop!
|
|
40
|
+
@running = false
|
|
41
|
+
@queue.push(:stop)
|
|
42
|
+
@worker_thread&.join(2)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
protected
|
|
46
|
+
|
|
47
|
+
def process_event(_event)
|
|
48
|
+
raise NotImplementedError, "Subclasses must implement #process_event"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Async Audit Logger Sidecar
|
|
53
|
+
class AuditLogger < Base
|
|
54
|
+
attr_reader :log_file
|
|
55
|
+
|
|
56
|
+
def initialize(log_file = "log/agent_audit.jsonl")
|
|
57
|
+
@log_file = File.expand_path(log_file)
|
|
58
|
+
FileUtils.mkdir_p(File.dirname(@log_file)) rescue nil
|
|
59
|
+
super("AuditLoggerSidecar")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
protected
|
|
63
|
+
|
|
64
|
+
def process_event(event)
|
|
65
|
+
File.open(@log_file, "a") do |f|
|
|
66
|
+
f.puts(JSON.generate(event))
|
|
67
|
+
f.flush
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Async Vulnerability & Code Quality Scanner Sidecar
|
|
73
|
+
class VulnerabilityScanner < Base
|
|
74
|
+
attr_reader :scanned_events
|
|
75
|
+
|
|
76
|
+
def initialize
|
|
77
|
+
@scanned_events = []
|
|
78
|
+
super("VulnerabilityScannerSidecar")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
protected
|
|
82
|
+
|
|
83
|
+
def process_event(event)
|
|
84
|
+
return unless event[:type] == :tool_executed
|
|
85
|
+
|
|
86
|
+
tool_name = event.dig(:payload, :tool_name)
|
|
87
|
+
params = event.dig(:payload, :params)
|
|
88
|
+
@scanned_events << { tool: tool_name, params: params, verified: true }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Antigravity
|
|
6
|
+
class Skill
|
|
7
|
+
attr_reader :name, :description, :instructions, :path
|
|
8
|
+
|
|
9
|
+
def initialize(path)
|
|
10
|
+
@path = File.expand_path(path)
|
|
11
|
+
parse_skill_file
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.load(path)
|
|
15
|
+
new(path)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def parse_skill_file
|
|
21
|
+
skill_file = File.join(@path, "SKILL.md")
|
|
22
|
+
raise ArgumentError, "SKILL.md not found at #{@path}" unless File.exist?(skill_file)
|
|
23
|
+
|
|
24
|
+
content = File.read(skill_file)
|
|
25
|
+
if content =~ /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m
|
|
26
|
+
front_matter = YAML.safe_load(Regexp.last_match(1))
|
|
27
|
+
@name = front_matter["name"]
|
|
28
|
+
@description = front_matter["description"]
|
|
29
|
+
@instructions = Regexp.last_match.post_match.strip
|
|
30
|
+
else
|
|
31
|
+
@name = File.basename(@path)
|
|
32
|
+
@description = ""
|
|
33
|
+
@instructions = content.strip
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Antigravity
|
|
4
|
+
class Tool
|
|
5
|
+
def self.inherited(subclass)
|
|
6
|
+
subclass.extend(ClassMethods)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
def name(val = nil, desc: nil)
|
|
11
|
+
if val
|
|
12
|
+
@tool_name = val.to_s
|
|
13
|
+
@tool_description = desc if desc
|
|
14
|
+
end
|
|
15
|
+
@tool_name || self.name&.split("::")&.last&.gsub(/(.)([A-Z])/, '\1_\2')&.downcase || "custom_tool"
|
|
16
|
+
end
|
|
17
|
+
alias tool_name name
|
|
18
|
+
|
|
19
|
+
def desc(val = nil)
|
|
20
|
+
@tool_description = val if val
|
|
21
|
+
@tool_description || ""
|
|
22
|
+
end
|
|
23
|
+
alias tool_description desc
|
|
24
|
+
|
|
25
|
+
def param(name, type: :string, description: "", required: true)
|
|
26
|
+
@parameters ||= {}
|
|
27
|
+
@parameters[name.to_sym] = {
|
|
28
|
+
type: type.to_s,
|
|
29
|
+
description: description,
|
|
30
|
+
required: required
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def parameters
|
|
35
|
+
@parameters || {}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_json_schema
|
|
39
|
+
properties = {}
|
|
40
|
+
required_params = []
|
|
41
|
+
|
|
42
|
+
parameters.each do |param_name, spec|
|
|
43
|
+
properties[param_name] = {
|
|
44
|
+
type: spec[:type],
|
|
45
|
+
description: spec[:description]
|
|
46
|
+
}
|
|
47
|
+
required_params << param_name.to_s if spec[:required]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
{
|
|
51
|
+
name: name,
|
|
52
|
+
description: desc,
|
|
53
|
+
parameters: {
|
|
54
|
+
type: "object",
|
|
55
|
+
properties: properties,
|
|
56
|
+
required: required_params
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def tool_name
|
|
63
|
+
self.class.name
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def to_json_schema
|
|
67
|
+
self.class.to_json_schema
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Factory for dynamic Proc-based tools
|
|
71
|
+
class Dynamic < Tool
|
|
72
|
+
attr_reader :block
|
|
73
|
+
|
|
74
|
+
def initialize(name, description: "", &block)
|
|
75
|
+
super()
|
|
76
|
+
@dynamic_name = name.to_s
|
|
77
|
+
@dynamic_description = description
|
|
78
|
+
@block = block
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def tool_name
|
|
82
|
+
@dynamic_name
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def call(params)
|
|
86
|
+
@block.call(params)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def to_json_schema
|
|
90
|
+
{
|
|
91
|
+
name: @dynamic_name,
|
|
92
|
+
description: @dynamic_description,
|
|
93
|
+
parameters: {
|
|
94
|
+
type: "object",
|
|
95
|
+
properties: {}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
data/lib/antigravity.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "antigravity/version"
|
|
4
|
+
require_relative "antigravity/config"
|
|
5
|
+
require_relative "antigravity/message"
|
|
6
|
+
require_relative "antigravity/harness"
|
|
7
|
+
require_relative "antigravity/hooks"
|
|
8
|
+
require_relative "antigravity/safety"
|
|
9
|
+
require_relative "antigravity/sidecar"
|
|
10
|
+
require_relative "antigravity/tool"
|
|
11
|
+
require_relative "antigravity/skill"
|
|
12
|
+
require_relative "antigravity/client"
|
|
13
|
+
require_relative "antigravity/agent"
|
|
14
|
+
|
|
15
|
+
module Antigravity
|
|
16
|
+
class << self
|
|
17
|
+
def configure
|
|
18
|
+
yield(config)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def config
|
|
22
|
+
@config ||= Config.new
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: antigravity-sdk
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Riccardo Carlesso
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: faye-websocket
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.11'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.11'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: json
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.6'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.6'
|
|
40
|
+
description: An elegant, Ruby-like SDK for building autonomous AI agents with Google
|
|
41
|
+
Antigravity.
|
|
42
|
+
email:
|
|
43
|
+
- ricc@google.com
|
|
44
|
+
executables: []
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- lib/antigravity.rb
|
|
49
|
+
- lib/antigravity/agent.rb
|
|
50
|
+
- lib/antigravity/client.rb
|
|
51
|
+
- lib/antigravity/config.rb
|
|
52
|
+
- lib/antigravity/harness.rb
|
|
53
|
+
- lib/antigravity/hooks.rb
|
|
54
|
+
- lib/antigravity/message.rb
|
|
55
|
+
- lib/antigravity/safety.rb
|
|
56
|
+
- lib/antigravity/sidecar.rb
|
|
57
|
+
- lib/antigravity/skill.rb
|
|
58
|
+
- lib/antigravity/tool.rb
|
|
59
|
+
- lib/antigravity/version.rb
|
|
60
|
+
homepage: https://github.com/palladius/antigravity-ruby-sdk
|
|
61
|
+
licenses:
|
|
62
|
+
- Apache-2.0
|
|
63
|
+
metadata:
|
|
64
|
+
homepage_uri: https://github.com/palladius/antigravity-ruby-sdk
|
|
65
|
+
source_code_uri: https://github.com/palladius/antigravity-ruby-sdk
|
|
66
|
+
changelog_uri: https://github.com/palladius/antigravity-ruby-sdk/blob/main/CHANGELOG.md
|
|
67
|
+
rdoc_options: []
|
|
68
|
+
require_paths:
|
|
69
|
+
- lib
|
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: 3.2.0
|
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '0'
|
|
80
|
+
requirements: []
|
|
81
|
+
rubygems_version: 3.6.9
|
|
82
|
+
specification_version: 4
|
|
83
|
+
summary: Google Antigravity SDK for Ruby
|
|
84
|
+
test_files: []
|