antigravity-sdk 0.3.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.
@@ -1,23 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Antigravity
4
+ # Represents a response message from the agent.
5
+ # Mirrors Python SDK's ChatResponse with metadata.
4
6
  class Message < Base
7
+ attr_accessor :role, :content, :thinking, :tool_calls, :model_id, :tokens,
8
+ :steps, :tool_calls_count, :usage, :delta
5
9
 
6
- attr_accessor :role, :content, :thinking, :tool_calls, :model_id, :tokens
7
-
8
- def initialize(role: :assistant, content: "", thinking: "", tool_calls: [], model_id: nil)
10
+ def initialize(role: :assistant, content: '', thinking: '', tool_calls: [],
11
+ model_id: nil, steps: [], tool_calls_count: 0, usage: nil,
12
+ delta: false)
9
13
  @role = role
10
14
  @content = content
11
15
  @thinking = thinking
12
16
  @tool_calls = tool_calls
13
17
  @model_id = model_id
14
18
  @tokens = { input: 0, output: 0 }
19
+ @steps = steps
20
+ @tool_calls_count = tool_calls_count
21
+ @usage = usage || { prompt_token_count: 0, candidates_token_count: 0, total_token_count: 0 }
22
+ @delta = delta
23
+ end
24
+
25
+ # Is this a streaming delta (partial) or a complete response?
26
+ def delta?
27
+ @delta
15
28
  end
16
29
  end
17
30
 
31
+ # Backward-compatible alias for streaming chunks
18
32
  class Chunk < Message
19
- def initialize(role: :assistant, content: "", thinking: "", tool_calls: [], model_id: nil)
20
- super(role: role, content: content, thinking: thinking, tool_calls: tool_calls, model_id: model_id)
33
+ def initialize(**kwargs)
34
+ super(**kwargs, delta: true)
21
35
  end
22
36
  end
23
37
  end
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'version'
4
+
5
+ module Antigravity
6
+ # Hand-rolled protobuf encoding/decoding for the 2 stdio handshake messages:
7
+ # InputConfig (SDK → localharness via stdin)
8
+ # OutputConfig (localharness → SDK via stdout)
9
+ #
10
+ # Uses raw protobuf wire format to avoid the google-protobuf gem dependency.
11
+ # See: https://github.com/palladius/antigravity-ruby-sdk/issues/7
12
+ #
13
+ # Wire format reference: https://protobuf.dev/programming-guides/encoding/
14
+ # Varint: wire type 0, tag = (field_number << 3) | 0
15
+ # Length-delimited: wire type 2, tag = (field_number << 3) | 2
16
+ class Protocol
17
+ # --- Encoding: InputConfig ---
18
+ # message InputConfig {
19
+ # string storage_directory = 1;
20
+ # uint32 port = 2; # optional, we leave 0
21
+ # string bind_address = 3; # default "localhost"
22
+ # ClientInfo client_info = 4;
23
+ # map<string, string> env = 5;
24
+ # }
25
+ # message ClientInfo {
26
+ # string language = 1;
27
+ # string version = 2;
28
+ # string language_version = 3;
29
+ # string os = 4;
30
+ # string os_version = 5;
31
+ # }
32
+ def self.encode_input_config(storage_directory:, bind_address: 'localhost', env: {})
33
+ buf = ''.b
34
+
35
+ # field 1: storage_directory (string)
36
+ buf << encode_string_field(1, storage_directory)
37
+
38
+ # field 3: bind_address (string, default "localhost")
39
+ buf << encode_string_field(3, bind_address)
40
+
41
+ # field 4: client_info (embedded message)
42
+ client_info = encode_client_info
43
+ buf << encode_bytes_field(4, client_info)
44
+
45
+ # field 5: env map entries (each is an embedded message with key=1, value=2)
46
+ env.each do |key, value|
47
+ entry = encode_string_field(1, key.to_s) + encode_string_field(2, value.to_s)
48
+ buf << encode_bytes_field(5, entry)
49
+ end
50
+
51
+ # Length-prefix: 4-byte little-endian uint32
52
+ [buf.bytesize].pack('V') + buf
53
+ end
54
+
55
+ # --- Decoding: OutputConfig ---
56
+ # message OutputConfig {
57
+ # int32 port = 1;
58
+ # string api_key = 2;
59
+ # }
60
+ def self.decode_output_config(data)
61
+ raise ProtocolError, 'Data too short for length prefix' if data.bytesize < 4
62
+
63
+ declared_len = data[0..3].unpack1('V')
64
+ payload = data[4..]
65
+
66
+ raise ProtocolError, "Truncated payload: expected #{declared_len}, got #{payload&.bytesize || 0}" if payload.nil? || payload.bytesize < declared_len
67
+
68
+ result = { port: 0, api_key: '' }
69
+ pos = 0
70
+
71
+ while pos < declared_len
72
+ tag_byte, new_pos = decode_varint(payload, pos)
73
+ pos = new_pos
74
+ field_number = tag_byte >> 3
75
+ wire_type = tag_byte & 0x07
76
+
77
+ case wire_type
78
+ when 0 # varint
79
+ value, pos = decode_varint(payload, pos)
80
+ result[:port] = value if field_number == 1
81
+ when 2 # length-delimited
82
+ length, pos = decode_varint(payload, pos)
83
+ value = payload[pos, length]
84
+ pos += length
85
+ result[:api_key] = value.force_encoding('UTF-8') if field_number == 2
86
+ else
87
+ raise ProtocolError, "Unsupported wire type #{wire_type} at position #{pos}"
88
+ end
89
+ end
90
+
91
+ result
92
+ end
93
+
94
+ # Read a length-prefixed frame from an IO (blocking).
95
+ # Returns the raw payload bytes.
96
+ def self.read_length_prefixed(io, timeout: 10)
97
+ len_bytes = read_exactly(io, 4, timeout: timeout)
98
+ raise ProtocolError, 'EOF reading length prefix' unless len_bytes&.bytesize == 4
99
+
100
+ payload_len = len_bytes.unpack1('V')
101
+ raise ProtocolError, "Unreasonable payload length: #{payload_len}" if payload_len > 1_000_000
102
+
103
+ frame = [payload_len].pack('V') + read_exactly(io, payload_len, timeout: timeout)
104
+ frame
105
+ end
106
+
107
+ class << self
108
+ private
109
+
110
+ def encode_client_info
111
+ buf = ''.b
112
+ buf << encode_string_field(1, 'ruby')
113
+ buf << encode_string_field(2, Antigravity::VERSION)
114
+ buf << encode_string_field(3, RUBY_VERSION)
115
+ buf << encode_string_field(4, ruby_platform_os)
116
+ buf << encode_string_field(5, RUBY_PLATFORM)
117
+ buf
118
+ end
119
+
120
+ def ruby_platform_os
121
+ case RUBY_PLATFORM
122
+ when /darwin/i then 'macos'
123
+ when /linux/i then 'linux'
124
+ when /win/i then 'windows'
125
+ else RUBY_PLATFORM
126
+ end
127
+ end
128
+
129
+ # Encode a string field: tag + varint length + UTF-8 bytes
130
+ def encode_string_field(field_number, value)
131
+ encode_bytes_field(field_number, value.to_s.encode('UTF-8').b)
132
+ end
133
+
134
+ # Encode a length-delimited field: tag + varint length + raw bytes
135
+ def encode_bytes_field(field_number, bytes)
136
+ tag = (field_number << 3) | 2 # wire type 2 = length-delimited
137
+ encode_varint_bytes(tag) + encode_varint_bytes(bytes.bytesize) + bytes
138
+ end
139
+
140
+ # Encode an integer as varint bytes
141
+ def encode_varint_bytes(value)
142
+ bytes = []
143
+ loop do
144
+ byte = value & 0x7F
145
+ value >>= 7
146
+ byte |= 0x80 if value > 0
147
+ bytes << byte
148
+ break if value == 0
149
+ end
150
+ bytes.pack('C*')
151
+ end
152
+
153
+ # Decode a varint starting at position, returns [value, new_position]
154
+ def decode_varint(data, pos)
155
+ result = 0
156
+ shift = 0
157
+ loop do
158
+ raise ProtocolError, "Varint extends past end of data at pos #{pos}" if pos >= data.bytesize
159
+
160
+ byte = data.getbyte(pos)
161
+ pos += 1
162
+ result |= (byte & 0x7F) << shift
163
+ break if (byte & 0x80) == 0
164
+
165
+ shift += 7
166
+ raise ProtocolError, 'Varint too long' if shift >= 64
167
+ end
168
+ [result, pos]
169
+ end
170
+
171
+ # Read exactly n bytes from IO with timeout
172
+ def read_exactly(io, n, timeout: 10)
173
+ buf = ''.b
174
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
175
+ while buf.bytesize < n
176
+ remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
177
+ raise ProtocolError, "Timeout reading #{n} bytes from harness" if remaining <= 0
178
+
179
+ ready = IO.select([io], nil, nil, [remaining, 0.1].min)
180
+ if ready
181
+ chunk = io.read_nonblock(n - buf.bytesize, exception: false)
182
+ case chunk
183
+ when String then buf << chunk
184
+ when :wait_readable then next
185
+ when nil then raise ProtocolError, 'EOF while reading from harness'
186
+ end
187
+ end
188
+ end
189
+ buf
190
+ end
191
+ end
192
+ end
193
+ end
@@ -3,30 +3,78 @@
3
3
  require "yaml"
4
4
 
5
5
  module Antigravity
6
+ # Represents an Agent Skill loaded from a SKILL.md file or defined inline.
7
+ # Spec: https://agentskills.io/specification
8
+ #
9
+ # A skill directory must contain a SKILL.md with YAML frontmatter:
10
+ # ---
11
+ # name: my-skill
12
+ # description: What this skill does
13
+ # ---
14
+ # # Instructions in markdown...
6
15
  class Skill < Base
7
16
 
8
- attr_reader :name, :description, :instructions, :path
17
+ attr_reader :name, :description, :instructions, :path, :metadata
9
18
 
19
+ # Load a skill from a directory containing SKILL.md.
20
+ # @param path [String] path to skill directory
10
21
  def initialize(path)
11
22
  @path = File.expand_path(path)
23
+ @metadata = {}
12
24
  parse_skill_file
13
25
  end
14
26
 
27
+ # Factory: load from directory path.
15
28
  def self.load(path)
16
29
  new(path)
17
30
  end
18
31
 
32
+ # Factory: create an inline skill (no file needed).
33
+ # @param name [String] skill name (lowercase, hyphenated)
34
+ # @param description [String] what the skill does
35
+ # @param instructions [String] the skill instructions (markdown)
36
+ # @return [Skill] an inline skill instance
37
+ def self.inline(name:, description:, instructions:)
38
+ skill = allocate
39
+ skill.send(:init_inline, name: name, description: description, instructions: instructions)
40
+ skill
41
+ end
42
+
43
+ # Check if a directory is a valid skill (contains SKILL.md).
44
+ # @param path [String] directory path to check
45
+ # @return [Boolean]
46
+ def self.skill_dir?(path)
47
+ SkillResolver.skill_dir?(path)
48
+ end
49
+
50
+ def to_s
51
+ "#<Skill name=#{@name.inspect} path=#{@path.inspect}>"
52
+ end
53
+
54
+ def inspect
55
+ "#<Antigravity::Skill name=#{@name.inspect} description=#{@description.inspect} path=#{@path.inspect}>"
56
+ end
57
+
19
58
  private
20
59
 
60
+ def init_inline(name:, description:, instructions:)
61
+ @name = name
62
+ @description = description
63
+ @instructions = instructions
64
+ @path = nil # inline skills have no path
65
+ @metadata = {}
66
+ end
67
+
21
68
  def parse_skill_file
22
69
  skill_file = File.join(@path, "SKILL.md")
23
70
  raise ArgumentError, "SKILL.md not found at #{@path}" unless File.exist?(skill_file)
24
71
 
25
- content = File.read(skill_file)
72
+ content = File.read(skill_file, encoding: 'UTF-8')
26
73
  if content =~ /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m
27
- front_matter = YAML.safe_load(Regexp.last_match(1))
28
- @name = front_matter["name"]
29
- @description = front_matter["description"]
74
+ front_matter = YAML.safe_load(Regexp.last_match(1)) || {}
75
+ @name = front_matter["name"] || File.basename(@path)
76
+ @description = front_matter["description"] || ""
77
+ @metadata = front_matter.fetch("metadata", {})
30
78
  @instructions = Regexp.last_match.post_match.strip
31
79
  else
32
80
  @name = File.basename(@path)
@@ -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
@@ -7,6 +7,12 @@ module Antigravity
7
7
  super
8
8
  subclass.extend(ClassMethods)
9
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
10
16
  end
11
17
 
12
18
  module ClassMethods
@@ -72,12 +78,13 @@ module Antigravity
72
78
 
73
79
  # Factory for dynamic Proc-based tools
74
80
  class Dynamic < Tool
75
- attr_reader :block
81
+ attr_reader :block, :params
76
82
 
77
- def initialize(name, description: "", &block)
83
+ def initialize(name, description: '', params: {}, &block)
78
84
  super()
79
85
  @dynamic_name = name.to_s
80
86
  @dynamic_description = description
87
+ @params = params
81
88
  @block = block
82
89
  end
83
90
 
@@ -85,17 +92,37 @@ module Antigravity
85
92
  @dynamic_name
86
93
  end
87
94
 
88
- def call(params)
89
- @block.call(params)
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
90
105
  end
91
106
 
92
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
+
93
119
  {
94
120
  name: @dynamic_name,
95
121
  description: @dynamic_description,
96
122
  parameters: {
97
- type: "object",
98
- properties: {}
123
+ type: 'object',
124
+ properties: properties,
125
+ required: required_params
99
126
  }
100
127
  }
101
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
@@ -9,15 +9,23 @@ end
9
9
  require_relative "antigravity/version"
10
10
  require_relative "antigravity/emojis"
11
11
  require_relative "antigravity/base"
12
+ require_relative "antigravity/errors"
12
13
  require_relative "antigravity/config"
13
14
  require_relative "antigravity/message"
15
+ require_relative "antigravity/protocol"
14
16
  require_relative "antigravity/harness"
15
17
  require_relative "antigravity/hooks"
16
18
  require_relative "antigravity/guards"
17
19
  require_relative "antigravity/sidecar"
18
20
  require_relative "antigravity/tool"
21
+ require_relative "antigravity/tool_runner"
22
+ require_relative "antigravity/skill_resolver"
19
23
  require_relative "antigravity/skill"
20
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"
21
29
  require_relative "antigravity/agent"
22
30
 
23
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.3.0
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: faye-websocket
13
+ name: websocket
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '0.11'
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: '0.11'
25
+ version: '1.2'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: json
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -64,14 +64,22 @@ files:
64
64
  - lib/antigravity/base.rb
65
65
  - lib/antigravity/client.rb
66
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
67
71
  - lib/antigravity/emojis.rb
72
+ - lib/antigravity/errors.rb
68
73
  - lib/antigravity/guards.rb
69
74
  - lib/antigravity/harness.rb
70
75
  - lib/antigravity/hooks.rb
71
76
  - lib/antigravity/message.rb
77
+ - lib/antigravity/protocol.rb
72
78
  - lib/antigravity/sidecar.rb
73
79
  - lib/antigravity/skill.rb
80
+ - lib/antigravity/skill_resolver.rb
74
81
  - lib/antigravity/tool.rb
82
+ - lib/antigravity/tool_runner.rb
75
83
  - lib/antigravity/version.rb
76
84
  homepage: https://github.com/palladius/antigravity-ruby-sdk
77
85
  licenses: