antigravity-sdk 0.2.0 → 0.4.2
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 +4 -4
- data/lib/antigravity/agent.rb +235 -24
- data/lib/antigravity/base.rb +18 -0
- data/lib/antigravity/config.rb +12 -1
- data/lib/antigravity/connection/binary_fetcher.rb +163 -0
- data/lib/antigravity/connection/local_connection.rb +184 -0
- data/lib/antigravity/connection/websocket_client.rb +159 -0
- data/lib/antigravity/conversation.rb +302 -0
- data/lib/antigravity/emojis.rb +46 -17
- data/lib/antigravity/errors.rb +27 -0
- data/lib/antigravity/guards.rb +82 -22
- data/lib/antigravity/hooks.rb +12 -0
- data/lib/antigravity/message.rb +20 -7
- data/lib/antigravity/protocol.rb +193 -0
- data/lib/antigravity/sidecar.rb +7 -5
- data/lib/antigravity/skill.rb +54 -7
- data/lib/antigravity/skill_resolver.rb +140 -0
- data/lib/antigravity/tool.rb +35 -11
- data/lib/antigravity/tool_runner.rb +59 -0
- data/lib/antigravity.rb +9 -0
- metadata +13 -4
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Antigravity
|
|
6
|
+
# Resolves skill paths from various inputs: single skill dir, container dir,
|
|
7
|
+
# or GitHub URL. Returns expanded absolute paths to skill directories.
|
|
8
|
+
#
|
|
9
|
+
# Resolution logic:
|
|
10
|
+
# 1. Path contains SKILL.md -> single skill
|
|
11
|
+
# 2. Path has a skills/ subfolder -> discover children with SKILL.md
|
|
12
|
+
# 3. Path children have SKILL.md -> flat container discovery
|
|
13
|
+
# 4. GitHub URL -> clone/cache then resolve locally
|
|
14
|
+
#
|
|
15
|
+
# Spec compliance: https://agentskills.io/specification
|
|
16
|
+
class SkillResolver
|
|
17
|
+
GITHUB_URL_PATTERN = %r{\Ahttps?://github\.com/}i
|
|
18
|
+
CACHE_DIR = File.expand_path('~/.antigravity/cache/ruby-sdk/skills')
|
|
19
|
+
|
|
20
|
+
# Resolve a path or URL to an array of valid skill directory paths.
|
|
21
|
+
# @param path_or_url [String] local path or GitHub URL
|
|
22
|
+
# @return [Array<String>] expanded absolute paths to skill directories
|
|
23
|
+
def self.resolve(path_or_url)
|
|
24
|
+
path_or_url = path_or_url.to_s.strip
|
|
25
|
+
|
|
26
|
+
if github_url?(path_or_url)
|
|
27
|
+
resolve_github(path_or_url)
|
|
28
|
+
else
|
|
29
|
+
resolve_local(path_or_url)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Discover all skill directories inside a container path.
|
|
34
|
+
# Checks for skills/ subfolder first, then scans children directly.
|
|
35
|
+
# @param container_path [String] path to scan for skills
|
|
36
|
+
# @return [Array<String>] sorted list of skill directory paths
|
|
37
|
+
def self.discover(container_path)
|
|
38
|
+
expanded = File.expand_path(container_path)
|
|
39
|
+
return [] unless File.directory?(expanded)
|
|
40
|
+
|
|
41
|
+
# Check for skills/ subfolder first (convention from agentskills.io)
|
|
42
|
+
skills_subdir = File.join(expanded, 'skills')
|
|
43
|
+
scan_dir = File.directory?(skills_subdir) ? skills_subdir : expanded
|
|
44
|
+
|
|
45
|
+
Dir.children(scan_dir)
|
|
46
|
+
.map { |child| File.join(scan_dir, child) }
|
|
47
|
+
.select { |child_path| skill_dir?(child_path) }
|
|
48
|
+
.sort
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Check if a directory is a valid skill (contains SKILL.md).
|
|
52
|
+
# @param path [String] directory path to check
|
|
53
|
+
# @return [Boolean]
|
|
54
|
+
def self.skill_dir?(path)
|
|
55
|
+
File.directory?(path) && File.exist?(File.join(path, 'SKILL.md'))
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Check if a string looks like a GitHub URL.
|
|
59
|
+
# @param str [String]
|
|
60
|
+
# @return [Boolean]
|
|
61
|
+
def self.github_url?(str)
|
|
62
|
+
str.match?(GITHUB_URL_PATTERN)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
class << self
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def resolve_local(path)
|
|
69
|
+
expanded = File.expand_path(path)
|
|
70
|
+
raise ArgumentError, "Skill path does not exist: #{expanded}" unless File.exist?(expanded)
|
|
71
|
+
raise ArgumentError, "Skill path is not a directory: #{expanded}" unless File.directory?(expanded)
|
|
72
|
+
|
|
73
|
+
# Case 1: Direct skill dir (has SKILL.md)
|
|
74
|
+
return [expanded] if skill_dir?(expanded)
|
|
75
|
+
|
|
76
|
+
# Case 2 & 3: Container dir -- discover children
|
|
77
|
+
discovered = discover(expanded)
|
|
78
|
+
return discovered unless discovered.empty?
|
|
79
|
+
|
|
80
|
+
raise ArgumentError, "No skills found at #{expanded}. Expected SKILL.md or a skills/ subfolder."
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Parse a GitHub URL into components.
|
|
84
|
+
# Supports:
|
|
85
|
+
# https://github.com/org/repo
|
|
86
|
+
# https://github.com/org/repo/tree/main/skills/my-skill
|
|
87
|
+
# https://github.com/org/repo/tree/main/skills
|
|
88
|
+
#
|
|
89
|
+
# Returns [org, repo, subpath_or_nil]
|
|
90
|
+
def parse_github_url(url)
|
|
91
|
+
# Strip trailing slash
|
|
92
|
+
url = url.chomp('/')
|
|
93
|
+
|
|
94
|
+
case url
|
|
95
|
+
when %r{github\.com/([^/]+)/([^/]+)/tree/[^/]+/(.+)}
|
|
96
|
+
# URL with tree/branch/subpath
|
|
97
|
+
[Regexp.last_match(1), Regexp.last_match(2), Regexp.last_match(3)]
|
|
98
|
+
when %r{github\.com/([^/]+)/([^/]+)(?:\.git)?/?$}
|
|
99
|
+
# Bare repo URL
|
|
100
|
+
[Regexp.last_match(1), Regexp.last_match(2), nil]
|
|
101
|
+
when %r{github\.com/([^/]+)/([^/]+)/(.+)}
|
|
102
|
+
# URL with subpath but no /tree/branch/ (e.g. from skill_name:)
|
|
103
|
+
[Regexp.last_match(1), Regexp.last_match(2), Regexp.last_match(3)]
|
|
104
|
+
else
|
|
105
|
+
raise ArgumentError, "Cannot parse GitHub URL: #{url}"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def resolve_github(url)
|
|
110
|
+
org, repo, subpath = parse_github_url(url)
|
|
111
|
+
local_repo = clone_or_update(org, repo)
|
|
112
|
+
|
|
113
|
+
# Resolve subpath within the cloned repo
|
|
114
|
+
target = subpath ? File.join(local_repo, subpath) : local_repo
|
|
115
|
+
resolve_local(target)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Clone (or update) a GitHub repo into the cache.
|
|
119
|
+
# Returns the local path to the cloned repo.
|
|
120
|
+
def clone_or_update(org, repo)
|
|
121
|
+
cache_path = File.join(CACHE_DIR, org, repo)
|
|
122
|
+
|
|
123
|
+
if File.directory?(File.join(cache_path, '.git'))
|
|
124
|
+
# Already cloned -- pull latest
|
|
125
|
+
system('git', '-C', cache_path, 'pull', '--ff-only', '--quiet',
|
|
126
|
+
out: File::NULL, err: File::NULL)
|
|
127
|
+
else
|
|
128
|
+
# Fresh clone
|
|
129
|
+
FileUtils.mkdir_p(File.dirname(cache_path))
|
|
130
|
+
clone_url = "https://github.com/#{org}/#{repo}.git"
|
|
131
|
+
success = system('git', 'clone', '--depth=1', '--quiet', clone_url, cache_path,
|
|
132
|
+
out: File::NULL, err: File::NULL)
|
|
133
|
+
raise "Failed to clone #{clone_url}. Check the URL and your network." unless success
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
cache_path
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
data/lib/antigravity/tool.rb
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Antigravity
|
|
4
|
-
class Tool
|
|
5
|
-
include Emojifiable
|
|
6
|
-
|
|
4
|
+
class Tool < Base
|
|
7
5
|
class << self
|
|
8
6
|
def inherited(subclass)
|
|
7
|
+
super
|
|
9
8
|
subclass.extend(ClassMethods)
|
|
10
9
|
end
|
|
10
|
+
|
|
11
|
+
# Factory method: define a tool inline with a block.
|
|
12
|
+
# Tool.define(:name, desc: '...', params: { city: { type: :string } }) { |city:| ... }
|
|
13
|
+
def define(name, desc: '', params: {}, &block)
|
|
14
|
+
Dynamic.new(name, description: desc, params: params, &block)
|
|
15
|
+
end
|
|
11
16
|
end
|
|
12
17
|
|
|
13
18
|
module ClassMethods
|
|
14
|
-
include Emojifiable::ClassMethods
|
|
15
|
-
|
|
16
19
|
def name(val = nil, desc: nil)
|
|
17
20
|
if val
|
|
18
21
|
@tool_name = val.to_s
|
|
@@ -75,12 +78,13 @@ module Antigravity
|
|
|
75
78
|
|
|
76
79
|
# Factory for dynamic Proc-based tools
|
|
77
80
|
class Dynamic < Tool
|
|
78
|
-
attr_reader :block
|
|
81
|
+
attr_reader :block, :params
|
|
79
82
|
|
|
80
|
-
def initialize(name, description:
|
|
83
|
+
def initialize(name, description: '', params: {}, &block)
|
|
81
84
|
super()
|
|
82
85
|
@dynamic_name = name.to_s
|
|
83
86
|
@dynamic_description = description
|
|
87
|
+
@params = params
|
|
84
88
|
@block = block
|
|
85
89
|
end
|
|
86
90
|
|
|
@@ -88,17 +92,37 @@ module Antigravity
|
|
|
88
92
|
@dynamic_name
|
|
89
93
|
end
|
|
90
94
|
|
|
91
|
-
def call(params)
|
|
92
|
-
@block
|
|
95
|
+
def call(params = nil, **kwargs)
|
|
96
|
+
return nil unless @block
|
|
97
|
+
|
|
98
|
+
if params && kwargs.empty?
|
|
99
|
+
# Old-style: tool.call({key: value})
|
|
100
|
+
@block.call(params)
|
|
101
|
+
else
|
|
102
|
+
# New-style: tool.call(key: value)
|
|
103
|
+
@block.call(**kwargs)
|
|
104
|
+
end
|
|
93
105
|
end
|
|
94
106
|
|
|
95
107
|
def to_json_schema
|
|
108
|
+
properties = {}
|
|
109
|
+
required_params = []
|
|
110
|
+
|
|
111
|
+
@params.each do |param_name, spec|
|
|
112
|
+
properties[param_name] = {
|
|
113
|
+
type: (spec[:type] || :string).to_s,
|
|
114
|
+
description: spec[:description] || spec[:desc] || ''
|
|
115
|
+
}
|
|
116
|
+
required_params << param_name.to_s unless spec[:required] == false
|
|
117
|
+
end
|
|
118
|
+
|
|
96
119
|
{
|
|
97
120
|
name: @dynamic_name,
|
|
98
121
|
description: @dynamic_description,
|
|
99
122
|
parameters: {
|
|
100
|
-
type:
|
|
101
|
-
properties:
|
|
123
|
+
type: 'object',
|
|
124
|
+
properties: properties,
|
|
125
|
+
required: required_params
|
|
102
126
|
}
|
|
103
127
|
}
|
|
104
128
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Antigravity
|
|
6
|
+
# Registers, resolves, and executes custom tools for agent callbacks.
|
|
7
|
+
# Tools are invoked by the harness when the model decides to call them.
|
|
8
|
+
class ToolRunner < Base
|
|
9
|
+
def initialize
|
|
10
|
+
@tools = {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Register a tool (Tool::Dynamic or any Tool subclass)
|
|
14
|
+
def register(tool)
|
|
15
|
+
name = tool.respond_to?(:tool_name) ? tool.tool_name : tool.to_s
|
|
16
|
+
raise ToolError, "Tool '#{name}' is already registered" if @tools.key?(name)
|
|
17
|
+
|
|
18
|
+
@tools[name] = tool
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Check if a tool is registered
|
|
22
|
+
def registered?(name)
|
|
23
|
+
@tools.key?(name.to_s)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Execute a tool by name with keyword args.
|
|
27
|
+
# Returns the result or a { error: "..." } hash on failure.
|
|
28
|
+
def execute(name, **kwargs)
|
|
29
|
+
tool = @tools[name.to_s]
|
|
30
|
+
raise ToolNotFoundError, "Tool '#{name}' not found" unless tool
|
|
31
|
+
|
|
32
|
+
tool.call(**kwargs)
|
|
33
|
+
rescue ToolNotFoundError
|
|
34
|
+
raise
|
|
35
|
+
rescue => e
|
|
36
|
+
{ error: e.message }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Generate harness-compatible tool definitions for HarnessConfig
|
|
40
|
+
def to_harness_tools
|
|
41
|
+
@tools.values.map do |tool|
|
|
42
|
+
schema = tool.to_json_schema
|
|
43
|
+
{
|
|
44
|
+
name: schema[:name],
|
|
45
|
+
description: schema[:description],
|
|
46
|
+
parametersJsonSchema: JSON.generate(schema[:parameters])
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def size
|
|
52
|
+
@tools.size
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def empty?
|
|
56
|
+
@tools.empty?
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/lib/antigravity.rb
CHANGED
|
@@ -8,15 +8,24 @@ end
|
|
|
8
8
|
|
|
9
9
|
require_relative "antigravity/version"
|
|
10
10
|
require_relative "antigravity/emojis"
|
|
11
|
+
require_relative "antigravity/base"
|
|
12
|
+
require_relative "antigravity/errors"
|
|
11
13
|
require_relative "antigravity/config"
|
|
12
14
|
require_relative "antigravity/message"
|
|
15
|
+
require_relative "antigravity/protocol"
|
|
13
16
|
require_relative "antigravity/harness"
|
|
14
17
|
require_relative "antigravity/hooks"
|
|
15
18
|
require_relative "antigravity/guards"
|
|
16
19
|
require_relative "antigravity/sidecar"
|
|
17
20
|
require_relative "antigravity/tool"
|
|
21
|
+
require_relative "antigravity/tool_runner"
|
|
22
|
+
require_relative "antigravity/skill_resolver"
|
|
18
23
|
require_relative "antigravity/skill"
|
|
19
24
|
require_relative "antigravity/client"
|
|
25
|
+
require_relative "antigravity/connection/binary_fetcher"
|
|
26
|
+
require_relative "antigravity/connection/websocket_client"
|
|
27
|
+
require_relative "antigravity/connection/local_connection"
|
|
28
|
+
require_relative "antigravity/conversation"
|
|
20
29
|
require_relative "antigravity/agent"
|
|
21
30
|
|
|
22
31
|
module Antigravity
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: antigravity-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2
|
|
4
|
+
version: 0.4.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Riccardo Carlesso
|
|
@@ -10,19 +10,19 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: websocket
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '1.2'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
25
|
+
version: '1.2'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: json
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -61,16 +61,25 @@ extra_rdoc_files: []
|
|
|
61
61
|
files:
|
|
62
62
|
- lib/antigravity.rb
|
|
63
63
|
- lib/antigravity/agent.rb
|
|
64
|
+
- lib/antigravity/base.rb
|
|
64
65
|
- lib/antigravity/client.rb
|
|
65
66
|
- lib/antigravity/config.rb
|
|
67
|
+
- lib/antigravity/connection/binary_fetcher.rb
|
|
68
|
+
- lib/antigravity/connection/local_connection.rb
|
|
69
|
+
- lib/antigravity/connection/websocket_client.rb
|
|
70
|
+
- lib/antigravity/conversation.rb
|
|
66
71
|
- lib/antigravity/emojis.rb
|
|
72
|
+
- lib/antigravity/errors.rb
|
|
67
73
|
- lib/antigravity/guards.rb
|
|
68
74
|
- lib/antigravity/harness.rb
|
|
69
75
|
- lib/antigravity/hooks.rb
|
|
70
76
|
- lib/antigravity/message.rb
|
|
77
|
+
- lib/antigravity/protocol.rb
|
|
71
78
|
- lib/antigravity/sidecar.rb
|
|
72
79
|
- lib/antigravity/skill.rb
|
|
80
|
+
- lib/antigravity/skill_resolver.rb
|
|
73
81
|
- lib/antigravity/tool.rb
|
|
82
|
+
- lib/antigravity/tool_runner.rb
|
|
74
83
|
- lib/antigravity/version.rb
|
|
75
84
|
homepage: https://github.com/palladius/antigravity-ruby-sdk
|
|
76
85
|
licenses:
|