Class: AgUi::A2ui::Catalog

Inherits:
Data
  • Object
show all
Defined in:
lib/ag_ui/a2ui/catalog.rb

Overview

The A2UI component catalog — the app's own UI vocabulary, served by the frontend (e.g. GET /api/copilotkit/catalog) and fetched once at boot, mirroring the Node sidecar (doc 09 §5): 20 retries x 3s, and on total failure A2UI DEGRADES (tool still injected, no schema) rather than failing the boot.

catalog = AgUi::A2ui::Catalog.fetch(url: ENV["AI_CATALOG_URL"])
catalog&.catalog_id  #=> "host://ai-catalog"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Attribute Details

#catalog_idObject (readonly)

Returns the value of attribute catalog_id

Returns:

  • (Object)

    the current value of catalog_id



19
20
21
# File 'lib/ag_ui/a2ui/catalog.rb', line 19

def catalog_id
  @catalog_id
end

#componentsObject (readonly)

Returns the value of attribute components

Returns:

  • (Object)

    the current value of components



19
20
21
# File 'lib/ag_ui/a2ui/catalog.rb', line 19

def components
  @components
end

Class Method Details

.fetch(url:, retries: 20, interval: 3, http: nil, logger: Console) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/ag_ui/a2ui/catalog.rb', line 29

def self.fetch(url:, retries: 20, interval: 3, http: nil, logger: Console)
  http ||= ->(u) { Net::HTTP.get_response(URI(u)) }

  attempt = 0
  loop do
    attempt += 1
    begin
      response = http.call(url)
      unless response.is_a?(Net::HTTPSuccess)
        raise "HTTP #{response.code}"
      end

      catalog = from_wire(JSON.parse(response.body))
      logger.info(
        self,
        "loaded A2UI catalog #{catalog.catalog_id} " \
        "(#{catalog.components.length} components) from #{url}",
      )
      break catalog
    rescue => e
      if attempt >= retries
        logger.warn(
          self,
          "A2UI catalog unreachable after #{attempt} attempts " \
          "(#{e.message}) — degrading (tool without schema)",
        )
        break nil
      end
      sleep interval
    end
  end
end

.from_wire(data) ⇒ Object

Wire shape: { "catalogId" => "...", "components" => ... }



21
22
23
24
25
26
27
# File 'lib/ag_ui/a2ui/catalog.rb', line 21

def self.from_wire(data)
  unless data.is_a?(Hash) && data["catalogId"] && data["components"]
    raise ArgumentError, "malformed catalog: expected catalogId + components"
  end

  new(catalog_id: data["catalogId"], components: data["components"])
end