Module: Ai2Web::Export
- Defined in:
- lib/ai2web/export.rb
Overview
Export adapters (RFC-0015): project the one canonical AI2Web manifest into other wire formats and discovery surfaces. Port of @ai2web/core export.ts.
Each export is a best-effort projection; where a target cannot represent a field, it is omitted rather than misstated. The canonical /ai2w manifest stays authoritative for execution.
Class Method Summary collapse
- .enabled_capabilities(m) ⇒ Object
-
.to_agent_json(manifest) ⇒ Object
Project the manifest to a generic
agent.jsonstyle capability document. -
.to_content_signals(manifest) ⇒ Object
Map usage_policy onto Content Signals tokens.
-
.to_discovery_link_header(manifest) ⇒ Object
Value for an HTTP Link header advertising the manifest to non-HTML clients.
-
.to_llms_txt(manifest) ⇒ Object
Project the manifest to an
llms.txtdocument: a plain-text summary and set of links a model can read for content and guidance. -
.to_oauth_protected_resource(manifest) ⇒ Object
OAuth 2.0 Protected Resource metadata (RFC 9728), for /.well-known/oauth-protected-resource.
-
.to_robots_txt(manifest) ⇒ Object
A robots.txt FRAGMENT carrying the usage policy and a pointer to the manifest.
Class Method Details
.enabled_capabilities(m) ⇒ Object
12 13 14 15 |
# File 'lib/ai2web/export.rb', line 12 def enabled_capabilities(m) caps = m["capabilities"].is_a?(Hash) ? m["capabilities"] : {} caps.select { |_k, v| Util.enabled?(v) }.keys end |
.to_agent_json(manifest) ⇒ Object
Project the manifest to a generic agent.json style capability document. Best-effort,
format-neutral projection of identity, capabilities, actions (with bindings), knowledge and
policies. Consent/governance a target cannot express are carried as a policies object
rather than dropped silently.
55 56 57 58 59 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 |
# File 'lib/ai2web/export.rb', line 55 def to_agent_json(manifest) m = Util.deep_stringify(manifest) site = m["site"].is_a?(Hash) ? m["site"] : {} actions = m["actions"].is_a?(Array) ? m["actions"] : [] = m["consent"].is_a?(Hash) ? m["consent"] : {} { "schema" => "agent-capabilities", "name" => site["name"], "description" => site["description"], "url" => site["url"], "identity" => m["identity"], "capabilities" => enabled_capabilities(m), "actions" => actions.map do |a| bindings = a["bindings"].is_a?(Array) && !a["bindings"].empty? ? a["bindings"] : [{ "kind" => "rest", "ref" => a["endpoint"] }] { "name" => a["name"], "intent" => a["intent"], "description" => a["description"], "risk" => a["risk"], "requires_consent" => a["requires_user_approval"], "requires_auth" => a["requires_auth"], "input_schema" => a["input_schema"], "bindings" => bindings } end, "knowledge" => m["knowledge"], "transports" => m["transports"], "policies" => { "consent" => ["requires_user_approval_for"], "governance" => m["governance"], "usage" => m["usage_policy"], "legal" => m["legal"] } } end |
.to_content_signals(manifest) ⇒ Object
Map usage_policy onto Content Signals tokens. search stays yes because AI2Web exists to
be discoverable; the AI signals are only asserted when the manifest states them, so an
unset policy is never reported as a refusal. Nil when no policy is declared.
128 129 130 131 132 133 134 135 136 137 |
# File 'lib/ai2web/export.rb', line 128 def to_content_signals(manifest) m = Util.deep_stringify(manifest) p = m["usage_policy"] return nil unless p.is_a?(Hash) && !p.empty? signals = ["search=yes"] signals << "ai-input=#{p["content_reproduction"] ? "yes" : "no"}" if [true, false].include?(p["content_reproduction"]) signals << "ai-train=#{p["model_training"] ? "yes" : "no"}" if [true, false].include?(p["model_training"]) signals.join(", ") end |
.to_discovery_link_header(manifest) ⇒ Object
Value for an HTTP Link header advertising the manifest to non-HTML clients.
155 156 157 158 159 |
# File 'lib/ai2web/export.rb', line 155 def to_discovery_link_header(manifest) m = Util.deep_stringify(manifest) site = m["site"].is_a?(Hash) ? m["site"] : {} "<#{Util.trim_url(site["url"].to_s)}/ai2w>; rel=\"ai2w\"" end |
.to_llms_txt(manifest) ⇒ Object
Project the manifest to an llms.txt document: a plain-text summary and set of links a model
can read for content and guidance. Reads only; no actions are exposed here.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/ai2web/export.rb', line 19 def to_llms_txt(manifest) m = Util.deep_stringify(manifest) site = m["site"].is_a?(Hash) ? m["site"] : {} base = Util.trim_url(site["url"].to_s) lines = ["# #{site["name"]}"] lines += ["", "> #{site["description"]}"] if Util.truthy?(site["description"]) caps = enabled_capabilities(m) lines += ["", "## Capabilities", *caps.map { |c| "- #{c}" }] unless caps.empty? knowledge = m["knowledge"].is_a?(Array) ? m["knowledge"] : [] unless knowledge.empty? lines << "" lines << "## Knowledge" knowledge.each do |k| ref = k["ref"].to_s ref = base + (ref.start_with?("/") ? "" : "/") + ref unless ref.start_with?("http") lines << "- [#{k["name"] || k["id"]}](#{ref})" end end actions = m["actions"].is_a?(Array) ? m["actions"] : [] unless actions.empty? lines << "" lines << "## Actions" actions.each { |a| lines << "- #{a["name"]}: #{a["description"]}" } end lines += ["", "## Discovery", "- Manifest: #{base}/ai2w"] "#{lines.join("\n")}\n" end |
.to_oauth_protected_resource(manifest) ⇒ Object
OAuth 2.0 Protected Resource metadata (RFC 9728), for /.well-known/oauth-protected-resource. MCP clients read this to discover which authorization server guards the resource before starting a flow.
Returns nil when the site does not advertise oauth2, so an auth surface the site cannot honour is never published.
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/ai2web/export.rb', line 97 def to_oauth_protected_resource(manifest) m = Util.deep_stringify(manifest) auth = m["auth"].is_a?(Hash) ? m["auth"] : {} return nil unless Array(auth["methods"]).include?("oauth2") site = m["site"].is_a?(Hash) ? m["site"] : {} base = Util.trim_url(site["url"].to_s) oauth2 = auth["oauth2"].is_a?(Hash) ? auth["oauth2"] : {} issuer = base authz = oauth2["authorization_url"].to_s unless authz.empty? begin u = URI.parse(authz) issuer = "#{u.scheme}://#{u.host}#{u.port && ![80, 443].include?(u.port) ? ":#{u.port}" : ""}" if u.scheme && u.host rescue URI::InvalidURIError # keep the site base as issuer end end doc = { "resource" => "#{base}/ai2w", "authorization_servers" => [issuer], "bearer_methods_supported" => ["header"] } scopes = oauth2["scopes"] doc["scopes_supported"] = Array(scopes) if scopes && !Array(scopes).empty? doc end |
.to_robots_txt(manifest) ⇒ Object
A robots.txt FRAGMENT carrying the usage policy and a pointer to the manifest. Append it to an existing robots.txt; it is never a replacement, and emits no Disallow rules.
141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/ai2web/export.rb', line 141 def to_robots_txt(manifest) m = Util.deep_stringify(manifest) site = m["site"].is_a?(Hash) ? m["site"] : {} base = Util.trim_url(site["url"].to_s) lines = ["# AI2Web usage policy, projected from #{base}/ai2w", "User-agent: *"] signals = to_content_signals(m) lines << "Content-Signal: #{signals}" unless signals.nil? lines << "# bulk_extraction: false - please use the /ai2w endpoints instead of crawling" if m["usage_policy"].is_a?(Hash) && m["usage_policy"]["bulk_extraction"] == false lines << "# AI2Web-Manifest: #{base}/ai2w" "#{lines.join("\n")}\n" end |