// The card an agent publishes about itself: where it listens, what it can do, and what it needs
// from a caller before it will talk. Security schemes and signatures are carried as they came,
// because a card must survive a round trip through this library unchanged.
///|
/// Where the card is served (section 8.2).
pub let card_path : String = "/.well-known/agent-card.json"
///|
/// One address an agent answers on (A2A `AgentInterface`).
#derive(Eq)
pub(all) struct AgentInterface {
url : String
protocol_binding : String
protocol_version : String
tenant : String?
}
///|
/// An interface speaking JSON-RPC at `url`.
pub fn AgentInterface::json_rpc(
url : String,
protocol_version~ : String,
) -> AgentInterface {
{ url, protocol_binding: "JSONRPC", protocol_version, tenant: None, }
}
///|
/// The provider of an agent (A2A `AgentProvider`).
#derive(Eq)
pub(all) struct AgentProvider {
organization : String
url : String
}
///|
/// A protocol extension an agent speaks (A2A `AgentExtension`).
#derive(Eq)
pub(all) struct AgentExtension {
uri : String
description : String?
required : Bool
params : Json?
}
///|
/// What optional parts of the protocol an agent does (A2A `AgentCapabilities`).
#derive(Eq)
pub(all) struct AgentCapabilities {
streaming : Bool
push_notifications : Bool
extended_agent_card : Bool
extensions : Array[AgentExtension]
}
///|
/// An agent that streams, sends push notifications and serves no extended card.
pub fn AgentCapabilities::streaming_and_push() -> AgentCapabilities {
{
streaming: true,
push_notifications: true,
extended_agent_card: false,
extensions: [],
}
}
///|
/// An agent that answers in a single body and sends nothing out of band.
pub fn AgentCapabilities::plain() -> AgentCapabilities {
{
streaming: false,
push_notifications: false,
extended_agent_card: false,
extensions: [],
}
}
///|
/// One thing an agent is good at (A2A `AgentSkill`).
#derive(Eq)
pub(all) struct AgentSkill {
id : String
name : String
description : String
tags : Array[String]
examples : Array[String]
input_modes : Array[String]
output_modes : Array[String]
}
///|
/// A skill named and described, tagged with `tags`, with no examples or modes of its own.
pub fn AgentSkill::new(
id : String,
name : String,
description : String,
tags : Array[String],
) -> AgentSkill {
{
id,
name,
description,
tags,
examples: [],
input_modes: [],
output_modes: [],
}
}
///|
/// An agent's self-description, the document a caller reads before talking to it (A2A `AgentCard`).
#derive(Eq)
pub(all) struct AgentCard {
name : String
description : String
supported_interfaces : Array[AgentInterface]
provider : AgentProvider?
version : String
documentation_url : String?
capabilities : AgentCapabilities
security_schemes : Json?
security_requirements : Json?
default_input_modes : Array[String]
default_output_modes : Array[String]
skills : Array[AgentSkill]
signatures : Json?
icon_url : String?
}
///|
/// A card with no provider, no security and no signatures, serving text in and out.
pub fn AgentCard::new(
name : String,
description : String,
version : String,
url : String,
skills : Array[AgentSkill],
) -> AgentCard {
{
name,
description,
supported_interfaces: [
AgentInterface::json_rpc(url, protocol_version="1.0"),
],
provider: None,
version,
documentation_url: None,
capabilities: AgentCapabilities::plain(),
security_schemes: None,
security_requirements: None,
default_input_modes: ["text/plain"],
default_output_modes: ["text/plain"],
skills,
signatures: None,
icon_url: None,
}
}
///|
/// The same card advertising `capabilities`.
pub fn AgentCard::capable(
self : AgentCard,
capabilities : AgentCapabilities,
) -> AgentCard {
{ ..self, capabilities, }
}
///|
/// The media types the card offers to take, from its defaults and its skills together.
pub fn AgentCard::input_modes(self : AgentCard) -> Array[String] {
let modes = self.default_input_modes.copy()
for skill in self.skills {
for mode in skill.input_modes {
if !modes.contains(mode) {
modes.push(mode)
}
}
}
modes
}
///|
/// The interface a client should try first, which is the one the card lists first.
pub fn AgentCard::preferred(self : AgentCard) -> AgentInterface? {
if self.supported_interfaces.length() > 0 {
Some(self.supported_interfaces[0])
} else {
None
}
}
///|
pub impl @json.ToJson for AgentInterface with fn to_json(self) {
let fields = object()
fields["url"] = Json::string(self.url)
fields["protocolBinding"] = Json::string(self.protocol_binding)
fields["protocolVersion"] = Json::string(self.protocol_version)
put_str(fields, "tenant", self.tenant)
Json::object(fields)
}
///|
pub impl @json.FromJson for AgentInterface with fn from_json(json, path) {
let fields = as_object(json, path)
{
url: need_str(fields, "url", path),
protocol_binding: need_str(fields, "protocolBinding", path),
protocol_version: opt_str(fields, "protocolVersion").unwrap_or("1.0"),
tenant: opt_str(fields, "tenant"),
}
}
///|
pub impl @json.ToJson for AgentProvider with fn to_json(self) {
Json::object({
"organization": Json::string(self.organization),
"url": Json::string(self.url),
})
}
///|
pub impl @json.FromJson for AgentProvider with fn from_json(json, path) {
let fields = as_object(json, path)
{
organization: need_str(fields, "organization", path),
url: need_str(fields, "url", path),
}
}
///|
pub impl @json.ToJson for AgentExtension with fn to_json(self) {
let fields = object()
fields["uri"] = Json::string(self.uri)
put_str(fields, "description", self.description)
if self.required {
fields["required"] = Json::boolean(true)
}
put_json(fields, "params", self.params)
Json::object(fields)
}
///|
pub impl @json.FromJson for AgentExtension with fn from_json(json, path) {
let fields = as_object(json, path)
{
uri: need_str(fields, "uri", path),
description: opt_str(fields, "description"),
required: opt_bool(fields, "required").unwrap_or(false),
params: fields.get("params"),
}
}
///|
pub impl @json.ToJson for AgentCapabilities with fn to_json(self) {
let fields = object()
// Each flag is written even when false: a client reads absence as "not supported", but a card
// that says `false` out loud saves the reader from guessing.
fields["streaming"] = Json::boolean(self.streaming)
fields["pushNotifications"] = Json::boolean(self.push_notifications)
fields["extendedAgentCard"] = Json::boolean(self.extended_agent_card)
if self.extensions.length() > 0 {
fields["extensions"] = Json::array(
self.extensions.map(AgentExtension::to_json),
)
}
Json::object(fields)
}
///|
pub impl @json.FromJson for AgentCapabilities with fn from_json(json, path) {
let fields = as_object(json, path)
{
streaming: opt_bool(fields, "streaming").unwrap_or(false),
push_notifications: opt_bool(fields, "pushNotifications").unwrap_or(false),
extended_agent_card: opt_bool(fields, "extendedAgentCard").unwrap_or(false),
extensions: read_opt_array(
fields,
"extensions",
path,
AgentExtension::from_json,
).unwrap_or([]),
}
}
///|
pub impl @json.ToJson for AgentSkill with fn to_json(self) {
let fields = object()
fields["id"] = Json::string(self.id)
fields["name"] = Json::string(self.name)
fields["description"] = Json::string(self.description)
fields["tags"] = strings(self.tags)
if self.examples.length() > 0 {
fields["examples"] = strings(self.examples)
}
if self.input_modes.length() > 0 {
fields["inputModes"] = strings(self.input_modes)
}
if self.output_modes.length() > 0 {
fields["outputModes"] = strings(self.output_modes)
}
Json::object(fields)
}
///|
pub impl @json.FromJson for AgentSkill with fn from_json(json, path) {
let fields = as_object(json, path)
{
id: need_str(fields, "id", path),
name: need_str(fields, "name", path),
description: need_str(fields, "description", path),
tags: strs_or_empty(fields, "tags"),
examples: strs_or_empty(fields, "examples"),
input_modes: strs_or_empty(fields, "inputModes"),
output_modes: strs_or_empty(fields, "outputModes"),
}
}
///|
pub impl @json.ToJson for AgentCard with fn to_json(self) {
let fields = object()
fields["name"] = Json::string(self.name)
fields["description"] = Json::string(self.description)
fields["supportedInterfaces"] = Json::array(
self.supported_interfaces.map(AgentInterface::to_json),
)
match self.provider {
Some(provider) => fields["provider"] = provider.to_json()
None => ()
}
fields["version"] = Json::string(self.version)
put_str(fields, "documentationUrl", self.documentation_url)
fields["capabilities"] = self.capabilities.to_json()
put_json(fields, "securitySchemes", self.security_schemes)
put_json(fields, "securityRequirements", self.security_requirements)
fields["defaultInputModes"] = strings(self.default_input_modes)
fields["defaultOutputModes"] = strings(self.default_output_modes)
fields["skills"] = Json::array(self.skills.map(AgentSkill::to_json))
put_json(fields, "signatures", self.signatures)
put_str(fields, "iconUrl", self.icon_url)
Json::object(fields)
}
///|
pub impl @json.FromJson for AgentCard with fn from_json(json, path) {
let fields = as_object(json, path)
{
name: need_str(fields, "name", path),
description: need_str(fields, "description", path),
supported_interfaces: read_array(
fields,
"supportedInterfaces",
path,
AgentInterface::from_json,
),
provider: read_opt(fields, "provider", path, AgentProvider::from_json),
version: need_str(fields, "version", path),
documentation_url: opt_str(fields, "documentationUrl"),
capabilities: match opt_object(fields, "capabilities") {
Some(capabilities) =>
AgentCapabilities::from_json(
Json::object(capabilities),
path.add_key("capabilities"),
)
None => raise wrong_type(path, "capabilities", "a capabilities object")
},
security_schemes: fields.get("securitySchemes"),
security_requirements: fields.get("securityRequirements"),
default_input_modes: strs_or_empty(fields, "defaultInputModes"),
default_output_modes: strs_or_empty(fields, "defaultOutputModes"),
skills: read_opt_array(fields, "skills", path, AgentSkill::from_json).unwrap_or([],
),
signatures: fields.get("signatures"),
icon_url: opt_str(fields, "iconUrl"),
}
}
///|
pub extend AgentInterface with @json.ToJson::{to_json}
///|
pub extend AgentProvider with @json.ToJson::{to_json}
///|
pub extend AgentExtension with @json.ToJson::{to_json}
///|
pub extend AgentCapabilities with @json.ToJson::{to_json}
///|
pub extend AgentSkill with @json.ToJson::{to_json}
///|
pub extend AgentCard with @json.ToJson::{to_json}
///|
pub extend AgentInterface with @json.FromJson::{from_json}
///|
pub extend AgentProvider with @json.FromJson::{from_json}
///|
pub extend AgentExtension with @json.FromJson::{from_json}
///|
pub extend AgentCapabilities with @json.FromJson::{from_json}
///|
pub extend AgentSkill with @json.FromJson::{from_json}
///|
pub extend AgentCard with @json.FromJson::{from_json}
///|
pub extend AgentInterface with Eq::{not_equal, equal}
///|
pub extend AgentProvider with Eq::{not_equal, equal}
///|
pub extend AgentExtension with Eq::{not_equal, equal}
///|
pub extend AgentCapabilities with Eq::{not_equal, equal}
///|
pub extend AgentSkill with Eq::{not_equal, equal}
///|
pub extend AgentCard with Eq::{not_equal, equal}