Class: Antigravity::Protocol

Inherits:
Object
  • Object
show all
Defined in:
lib/antigravity/protocol.rb

Overview

Hand-rolled protobuf encoding/decoding for the 2 stdio handshake messages:

InputConfig  (SDK  localharness via stdin)
OutputConfig (localharness  SDK via stdout)

Uses raw protobuf wire format to avoid the google-protobuf gem dependency. See: https://github.com/palladius/antigravity-ruby-sdk/issues/7

Wire format reference: https://protobuf.dev/programming-guides/encoding/ Varint: wire type 0, tag = (field_number << 3) | 0 Length-delimited: wire type 2, tag = (field_number << 3) | 2

Class Method Summary collapse

Class Method Details

.decode_output_config(data) ⇒ Object

--- Decoding: OutputConfig --- message OutputConfig { int32 port = 1; string api_key = 2; }

Raises:



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/antigravity/protocol.rb', line 60

def self.decode_output_config(data)
  raise ProtocolError, 'Data too short for length prefix' if data.bytesize < 4

  declared_len = data[0..3].unpack1('V')
  payload = data[4..]

  raise ProtocolError, "Truncated payload: expected #{declared_len}, got #{payload&.bytesize || 0}" if payload.nil? || payload.bytesize < declared_len

  result = { port: 0, api_key: '' }
  pos = 0

  while pos < declared_len
    tag_byte, new_pos = decode_varint(payload, pos)
    pos = new_pos
    field_number = tag_byte >> 3
    wire_type = tag_byte & 0x07

    case wire_type
    when 0 # varint
      value, pos = decode_varint(payload, pos)
      result[:port] = value if field_number == 1
    when 2 # length-delimited
      length, pos = decode_varint(payload, pos)
      value = payload[pos, length]
      pos += length
      result[:api_key] = value.force_encoding('UTF-8') if field_number == 2
    else
      raise ProtocolError, "Unsupported wire type #{wire_type} at position #{pos}"
    end
  end

  result
end

.encode_input_config(storage_directory:, bind_address: 'localhost', env: {}) ⇒ Object

--- Encoding: InputConfig --- message InputConfig { string storage_directory = 1; uint32 port = 2; # optional, we leave 0 string bind_address = 3; # default "localhost" ClientInfo client_info = 4; map<string, string> env = 5; } message ClientInfo { string language = 1; string version = 2; string language_version = 3; string os = 4; string os_version = 5; }



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/antigravity/protocol.rb', line 32

def self.encode_input_config(storage_directory:, bind_address: 'localhost', env: {})
  buf = ''.b

  # field 1: storage_directory (string)
  buf << encode_string_field(1, storage_directory)

  # field 3: bind_address (string, default "localhost")
  buf << encode_string_field(3, bind_address)

  # field 4: client_info (embedded message)
  client_info = encode_client_info
  buf << encode_bytes_field(4, client_info)

  # field 5: env map entries (each is an embedded message with key=1, value=2)
  env.each do |key, value|
    entry = encode_string_field(1, key.to_s) + encode_string_field(2, value.to_s)
    buf << encode_bytes_field(5, entry)
  end

  # Length-prefix: 4-byte little-endian uint32
  [buf.bytesize].pack('V') + buf
end

.read_length_prefixed(io, timeout: 10) ⇒ Object

Read a length-prefixed frame from an IO (blocking). Returns the raw payload bytes.

Raises:



96
97
98
99
100
101
102
103
104
105
# File 'lib/antigravity/protocol.rb', line 96

def self.read_length_prefixed(io, timeout: 10)
  len_bytes = read_exactly(io, 4, timeout: timeout)
  raise ProtocolError, 'EOF reading length prefix' unless len_bytes&.bytesize == 4

  payload_len = len_bytes.unpack1('V')
  raise ProtocolError, "Unreasonable payload length: #{payload_len}" if payload_len > 1_000_000

  frame = [payload_len].pack('V') + read_exactly(io, payload_len, timeout: timeout)
  frame
end