diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f19e8ff --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# Binaries +artf-agent +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool +coverage.out +coverage.html + +# Generated protobuf files (uncomment if you want to commit generated files) +# pkg/pb/**/*.pb.go + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log + +# Local environment +.env +.env.local + +# Temporary files +tmp/ +temp/ diff --git a/Agentic RTB Framework Version 1.0 for PUBLIC COMMENT.md b/Agentic RTB Framework Version 1.0 for PUBLIC COMMENT.md new file mode 100644 index 0000000..84a9613 --- /dev/null +++ b/Agentic RTB Framework Version 1.0 for PUBLIC COMMENT.md @@ -0,0 +1,531 @@ + + +# ![LogoDescription automatically generated][image1] + +# + +# **Agentic RTB Framework** + +## A specification for using agent-driven containers in OpenRTB and Digital Advertising + +**Version 1.0** +*Released November 12, 2025* + + +Please email support@iabtechlab.com for public comments and questions. This document is available online at [https://iabtechlab.com/standards/artf/](https://iabtechlab.com/standards/artf/) + + + + +**© 2025 IAB Technology Laboratory** + +# About this document {#about-this-document} + +The Agentic RTB Framework specification defines a foundation for implementing agent services which operate within a host platform and that the orchestrating platform can call directly to accomplish a shared goal. The model leverages containers which are deployed into the infrastructure of a host to enable delegation of critical aspects of bidstream processing to service agents in a consistent manner, with minimal cost, latency and operational impacts. The framework enables this by establishing standard requirements for container runtime behavior and by defining an API which enables reliable, protected and private bidstream mutation. + +With this approach, service providers package their offering once and deploy it to any standard-compliant platform, which means they are able to focus on their unique value proposition while offloading operational concerns and scaling to the host platforms. It also creates new possibilities for innovation because host platforms maintain control of data and SLAs and therefore can provide greater access to data and more interaction opportunities to service agents without concerns about leakage, misappropriation or latency. + +The Agentic RTB Framework provides significant value to host platforms which are able to “drop-in” new capabilities with minimal integration overhead and compose bid processing pipelines configured specifically for their target use cases. The standard also enables platforms to adapt quickly to changing market demands by simply adding, updating and removing components as needed, all while maintaining control of operational costs and requirements and without incurring significant integration overhead and cost. + +While this approach is agentic in nature, the primary focus here is on systematic agentic integration (service to service integration), but autonomic agentic functionality (model to service) is also envisioned as part of this specification as the integrating technology matures. + +There are many use cases; for example identity resolution provided by an agent, deal or segmentation activation, fraud detection pre-impression etc. This list of use cases is not fully enumerated here, because part of the goal of this specification is to allow new use cases to be integrated into the bid stream via the agentic framework. + +The specification aims to provide a general framework and best practices for deploying and operating these agents. It is not limited to a predefined set of use cases. Each use case is meant to be supported via an “intent” using the specification. + +Additionally, the specification describes a standard interface with which containers can be managed using AI agents. This enables sub millisecond real-time bidding operations driven by agentic systems. + +This document is primarily for technical audiences, in particular engineers and product managers wishing to implement products and features which can be solved with hosted containers and AI agents. The key takeaways for readers are: + +- Understanding why to use containers and AI agents for certain use cases +- Learning what a standard container deployment involves including digital formats for describing the container, requirements, and functions +- Learning how to declare container capabilities and service definitions +- Understanding example use cases and workflows +- Recommendations of best practices for facilitating adoption across the industry + +This document is developed by the IAB Tech Lab [Container Project Task Force](https://iabtechlab.com/working-groups/container-project-working-group/) which is a subgroup of the [Programmatic Supply Chain Working Group](https://iabtechlab.com/working-groups/programmatic-supply-chain-working-group/). + +**License** +Agentic RTB Framework document is licensed under a [Creative Commons Attribution 3.0 License](http://creativecommons.org/licenses/by/3.0/). To view a copy of this license, visit [creativecommons.org/licenses/by/3.0/](http://creativecommons.org/licenses/by/3.0/) or write to Creative Commons, 171 Second Street, Suite 300, San Francisco, CA 94105, USA. + +**Significant Contributors** +Joshua Prismon, *Index Exchange*; Joe Wilson, *Chalice*; Arpad Miklos, *The Trade Desk*; Brian May, *Individual*; Roni Gordon, *Index Exchange*; Ran Li, *Index Exchange*; Ben White, *OpenX* + +**IAB Tech Lab Leads** +Miguel Morales, Director Addressability & Privacy Enhancing Technologies (PETs) +Shailley Singh, EVP Product & COO + +**About IAB Tech Lab** +The IAB Technology Laboratory is a nonprofit research and development consortium charged with producing and helping companies implement global industry technical standards and solutions. The goal of the Tech Lab is to reduce friction associated with the digital advertising and marketing supply chain while contributing to the safe growth of an industry. + +The IAB Tech Lab spearheads the development of technical standards, creates and maintains a code library to assist in rapid, cost-effective implementation of IAB standards, and establishes a test platform for companies to evaluate the compatibility of their technology solutions with IAB standards, which for 18 years have been the foundation for interoperability and profitable growth in the digital advertising supply chain. Further details about the IAB Technology Lab can be found at [https://iabtechlab.com](https://iabtechlab.com). + +**Disclaimer** +THE STANDARDS, THE SPECIFICATIONS, THE MEASUREMENT GUIDELINES, AND ANY OTHER MATERIALS OR SERVICES PROVIDED TO OR USED BY YOU HEREUNDER (THE “PRODUCTS AND SERVICES”) ARE PROVIDED “AS IS” AND “AS AVAILABLE,” AND IAB TECHNOLOGY LABORATORY, INC. (“TECH LAB”) MAKES NO WARRANTY WITH RESPECT TO THE SAME AND HEREBY DISCLAIMS ANY AND ALL EXPRESS, IMPLIED, OR STATUTORY WARRANTIES, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AVAILABILITY, ERROR-FREE OR UNINTERRUPTED OPERATION, AND ANY WARRANTIES ARISING FROM A COURSE OF DEALING, COURSE OF PERFORMANCE, OR USAGE OF TRADE. TO THE EXTENT THAT TECH LAB MAY NOT AS A MATTER OF APPLICABLE LAW DISCLAIM ANY IMPLIED WARRANTY, THE SCOPE AND DURATION OF SUCH WARRANTY WILL BE THE MINIMUM PERMITTED UNDER SUCH LAW. THE PRODUCTS AND SERVICES DO NOT CONSTITUTE BUSINESS OR LEGAL ADVICE. TECH LAB DOES NOT WARRANT THAT THE PRODUCTS AND SERVICES PROVIDED TO OR USED BY YOU HEREUNDER SHALL CAUSE YOU AND/OR YOUR PRODUCTS OR SERVICES TO BE IN COMPLIANCE WITH ANY APPLICABLE LAWS, REGULATIONS, OR SELF-REGULATORY FRAMEWORKS, AND YOU ARE SOLELY RESPONSIBLE FOR COMPLIANCE WITH THE SAME, INCLUDING, BUT NOT LIMITED TO, DATA PROTECTION LAWS, SUCH AS THE PERSONAL INFORMATION PROTECTION AND ELECTRONIC DOCUMENTS ACT (CANADA), THE DATA PROTECTION DIRECTIVE (EU), THE E-PRIVACY DIRECTIVE (EU), THE GENERAL DATA PROTECTION REGULATION (EU), AND THE E-PRIVACY REGULATION (EU) AS AND WHEN THEY BECOME EFFECTIVE. + +# Glossary {#glossary} + +| Term | Description | +| :---- | :---- | +| *Agent* | A software service encapsulated in a container that performs a specific, autonomous function (e.g., fraud detection, deal curation) within the bidstream. Agents are designed to operate with explicit intents and within orchestrator-defined constraints. | +| *Agent Manifest* | A JSON structure embedded as metadata in a container image that defines an agent’s capabilities, resource requirements, intents, and dependencies. Used by orchestrators to deploy and manage containers safely. | +| *Agent Orchestrator* | The control layer within a host platform that manages the deployment, execution, and coordination of agent containers participating in the bidstream. | +| *Agent to Agent (A2A)* | Direct communication or coordination between two autonomous agents, enabling them to exchange data or decisions without requiring mediation by a central orchestrator. | +| *Agentic System* | A distributed architecture where autonomous agents make decisions independently toward shared goals, such as optimizing auction outcomes, while maintaining defined constraints and interoperability. | +| *Autonomous Agentic* | Refers to systems or agents that make independent, AI-driven decisions and actions without direct orchestration, operating based on learned models or contextual goals. | +| *Bidstream* | The flow of bid requests and responses exchanged between programmatic advertising entities (SSPs, DSPs, exchanges). In ARTF, agents can modify bidstream data under orchestrator supervision. | +| *Bidstream Mutation* | A proposed change (addition, deletion, or update) to data within the bidstream, expressed via an OpenRTB Patch. Each mutation is atomic and tied to a specific intent. | +| *Container* | A standardized, lightweight, and portable execution environment that packages an agent’s code, dependencies, and configuration into a single image. Containers conform to OCI runtime standards (e.g., Docker, Kubernetes). | +| *Intent* | A declarative statement describing why an agent proposes a mutation (e.g., “adjustBid,” “activateSegments,” “expireDeals”). Intents guide orchestrators’ decision-making about whether to accept or reject mutations. | +| *Manifest (Container Manifest)* | The metadata file (usually container.json) defining container resources, intents, dependencies, and health checks, enabling orchestrators to deploy the container consistently. | +| *MCP (Model Context Protocol)* | A protocol for structured model-to-agent communication using JSON-RPC. Complements gRPC and supports autonomic (AI-driven) agentic interactions, allowing models to orchestrate services directly. | +| *Mutation* | A single, atomic change proposed to a bid request or response. Includes fields such as intent, op (add/replace/remove), and path (semantic reference within OpenRTB payload). | +| *Orchestrator (Orchestrating Entity)* | The host platform (e.g., SSP, DSP, exchange) that manages the execution of agent containers, controls access, validates mutations, and decides whether to apply proposed changes. | +| *Patch* | A structured set of mutations proposed by an agent to modify an OpenRTB request or response. Each patch is atomic, traceable, and subject to orchestrator approval. | +| *Sidecar* | A secondary container that runs alongside a primary application container, providing auxiliary functions (e.g., monitoring, telemetry, or mediation). | +| *Structural Agentic* | Agentic systems where interactions are between structured services (service-to-service orchestration), rather than direct AI model control. | +| *Telemetry* | Monitoring data (metrics, traces, logs) emitted by containers to track performance, security, and decision impact across orchestrated systems. | +| *User Cohorts / Audience Segments* | Groupings of users with shared characteristics or behaviors that agents can activate or modify as part of audience segmentation use cases. | + +# + +# Table of Contents {#table-of-contents} + +[**About this document 2**](#about-this-document) + +[**Glossary 5**](#glossary) + +[**Table of Contents 6**](#table-of-contents) + +[**Introduction 7**](#introduction) + +[What is an agentic system? 7](#what-is-an-agentic-system?) + +[Requirements 7](#requirements) + +[What is a Container? 8](#what-is-a-container?) + +[Why Containers? 8](#why-containers?) + +[Integration into the Container Ecosystem 9](#integration-into-the-container-ecosystem) + +[Why OpenRTB Patch? 9](#why-openrtb-patch?) + +[Path Clarification 11](#path-clarification) + +[Why gRPC and MCP? 11](#why-grpc-and-mcp?) + +[**Agent Manifest 12**](#agent-manifest) + +[**Technical Implementation 13**](#technical-implementation) + +[Infrastructure Considerations 13](#infrastructure-considerations) + +[Manifest Requirements 13](#manifest-requirements) + +[Bidstream Integration 14](#bidstream-integration) + +[Service Naming 15](#service-naming) + +[API Design 15](#api-design) + +[Example Extension Point 15](#example-extension-point) + +[Request Message 16](#heading=h.cavzx66mi77z) + +[AgenticRTB Specific Requirements 16](#agenticrtb-specific-requirements) + +[Extension Response 17](#extension-response) + +[Example \- Audience Segments \- Activating Cohorts 18](#example---audience-segments---activating-cohorts) + +[Example \- Complex Orchestration 19](#example---complex-orchestration) + +[Manifest \- Container.json 20](#manifest---container.json) + +# Introduction {#introduction} + +## What is an agentic system? {#what-is-an-agentic-system?} + +The digital advertising ecosystem is built on a distributed system that allows for multi-party participation across a variety of business activities. This distributed system has common goals, but individual systems that are called by other partners to accomplish these goals. Each of these distributed systems also work to accomplish their own goals to accomplish their own mandates and make their own decisions to accomplish both the global goal (ie, a specific auction) as well as their own goals and mandates. + +A **structurally agentic system** is a distributed architecture where autonomous components operate with explicit mandates, decision-making authority, and structured delegation patterns to accomplish domain-specific goals within defined constraints. Supply-Side Platforms (SSPs) and Demand-Side Platforms (DSPs) function as independent agents that orchestrate real-time auctions by delegating specialized tasks to other agents through standardized protocols such as OpenRTB. Each agent maintains internal state, optimizes toward distinct objectives (e.g., maximizing publisher yield vs. advertiser ROI), and makes autonomous decisions within strict latency budgets while the system's overall behavior emerges from the interaction of these specialized components rather than centralized control. This architectural pattern enables composability, fault isolation, independent optimization, and the ability to integrate new capabilities without modifying core auction logic, making it fundamentally different from monolithic systems where a single component possesses global knowledge and decision-making authority. + +This standard introduces the ability to add new agents into existing orchestrations by introducing two new constructs \- containers and OpenRTB patch. The focus of this document is to enable Agentic extensibility for the RTB process, but both the container and the OpenRTB patch are a generic agentic approach, and can be used for non real-time bidding use cases as well. + +## Requirements {#requirements} + +For agentic extensibility to be adopted widely in the OpenRTB ecosystem, implementers need to adhere to a basic set of principles. These principles are: + +1) **Agents must be able to participate in the core bidstream.** The bidstream may have many different points of integration with containers. This standard is focused on entities that are transacting via the bidstream in real-time, and is not a general-purpose standard for containerized services at the edge. +2) **Agents must accomplish a specific goal.** Each agent must declare specific intents for itself, and any changes that it wants to make to auctions moving through the bidstream. The orchestrating entity may then choose to accept the change or not to facilitate multi-party participation. +3) **Agents must be composable and deployable in standard enterprise infrastructure.** Agents must be structured as Containers and must adhere to the standard OCI runtime and image specifications (i.e., Docker Containers) so they are manageable via distributed cluster orchestration systems such as Kubernetes, Docker Compose / Swarm or cloud based systems like Amazon’s Elastic Containers service. +4) **Agents must be performant and efficient**. Agents must communicate via a high performance protocol such as gRPC or in some cases MCP. Agents should be written in an efficient language and leverage an efficient ecosystem (for example, Rust, Go, or Java) or a highly optimized interpreted language. Agents should not consume resources they do not need. Orchestrating entities must provide guidance on expected response times to container providers. +5) **Agents must adhere to the policy of least-privilege and least-data. Agents will not have unfettered access to the outside world**, and must leverage appropriate services provided by the orchestrating entity. Orchestrating entities should implement appropriate measures to ensure containers adhere to this requirement. + +## What is a Container? {#what-is-a-container?} + +A container is a standard technology, originally popularized by Docker that provides an image-based, versioned, lightweight, and portable execution environment that encapsulates an application and all its dependencies—code, runtime, system tools, libraries, and settings—into a single package. This approach ensures that the containerized application behaves consistently across different computing environments, whether running locally, in a private data center, or in the cloud. + +Containers provide a form of operating system-level virtualization where each container runs in isolation with its own runtime image, while sharing the host OS kernel. They are built using Open Container Initiative (OCI) standards, which define image formats and runtime specifications, making containers interoperable across various orchestration platforms such as Kubernetes, Docker Compose, or cloud-based services like Amazon Elastic Container Service (ECS). + +In the context of OpenRTB and programmatic advertising, containers act as execution units for business logic that must interact with the bidstream in real-time. These execution units can host logic for bid modification, audience activation, deal curation, and other bidstream processes, while offering flexibility, modularity and simplified deployment for participants without requiring the container or the orchestrator to provide bespoke core platform infrastructure to each partner. + +### Why Containers? {#why-containers?} + +Containers enable a portable (capable of running in many environments), composable (can be combined with other containers or systems to provide value), dynamically scalable (capacity can be ramped up and down as demand does), and protected mode (manage ingress and egress, protect IP and sensitive data allowing execution of decisioning logic across diverse programmatic environments). Container’s standardized packaging allows partners to deploy once and operate across multiple orchestrating entities and cloud infrastructures without retooling, while composability ensures that logic from buyers, sellers, identity providers, and measurement vendors can be integrated into a cohesive, real-time auctioning workflow. Critically, containers encapsulate business logic in a self-contained image isolating proprietary IP while still allowing participation in bidstreams without requiring independent scaling. + +### Integration into the Container Ecosystem {#integration-into-the-container-ecosystem} + +To run a container from a partner company in an orchestrator's infrastructure, both parties must address specific requirements and restrictions: + +* The agent provider must ensure their application runs properly as a non-root user +* The orchestrator will never run the container as a root user +* The container must implement a [Kubernetes compatible health and readiness probes](https://kubernetes.io/docs/concepts/configuration/liveness-readiness-startup-probes/) (HTTP endpoints, TCP checks, or exec commands) +* Containers must follow the principle of least privilege and remove all unnecessary capabilities. +* The container must be built to handle graceful shutdowns, respect security contexts, and must not require privileged access or host network/PID namespaces. +* The container must run without external network access: all network ingress and egress is prohibited with the exception of service communications with the orchestrating entity. Agent providers and orchestrating entities may negotiate specific network access policies to facilitate efficient operation of containers in the orchestrator’s environment. + +On the orchestrating side, the orchestrator needs to implement appropriate RBAC policies that grant containers only the minimum required resource permissions. Network policies must be configured to control both ingress and egress traffic and explicitly define which services containers can communicate with and which ports are accessible. Depending on the required security level of network traffic control, the orchestrator may want to allow agent providers alternatives such as volume mappings with external data synchronization facilities to enable container data import and export. For secrets management, the orchestrator must securely inject any required credentials, API keys, or certificates using Kubernetes Secrets or similar methods, such as mounted secrets filesystem or secure environment variables) and will ensure they're properly scoped and rotated. Additionally, the orchestrator is expected to implement resource quotas and limits, use Pod Security Policies or Pod Security Standards or similar methods to enforce security constraints, and support monitoring and logging to track the container's behavior within their infrastructure. + +### Why OpenRTB Patch? {#why-openrtb-patch?} + +OpenRTB itself is a multi-party structurally agentic system. Complex interactions between many different parties are built into the OpenRTB and AdCOM domain models which describe the parameters of the real time bidding process. Introducing new agents into the existing system bypassing the full OpenRTB payload to those agents and the agent then returning the full, updated OpenRTB payload has a number of drawbacks: + +* Inefficiency of passing the full OpenRTB payload to every entity introduces considerable serialization and deserialization costs. +* Using a sequential approach for agentic calls has unacceptable performance implications +* Running multiple agentic calls with the same payload that all return a modified payload makes it extraordinarily difficult and expensive to understand the changes to the payload that each individual agent makes. +* The full OpenRTB payload exposes all information, including information that may not be germane to the agent's purpose or may contain competitive or restricted information. +* Data may need to be scrubbed for data privacy purposes as well. Reconstructing the payload would be expensive. +* Some information needed by the agent may not be part of the OpenRTB standard. +* It may be very difficult for orchestrating systems to understand *why* some changes are made. + +To address these changes, This standard envisions a protocol that meets the following requirements: + +1) Each Agent should only get the minimum data allowed by the orchestrator and requested by the agent provider, following the least-data principle. In particular, private and competitive signals must be removed prior to agentic involvement. +2) Agentic Changes to the OpenRTB payload must be isolated to ensure that each change can be evaluated or rejected independently. +3) Each agent should be decoupled from the processing pipeline implementation. An agent implementation should never know internal specifics such as call sites or internal structures of orchestration systems. +4) Agentic Changes to the OpenRTB payload must declare their intentions to ensure that orchestrators understand why proposed changes are made. + +To meet these requirements this standard extends the model by introducing a patching mechanism, the *OpenRTB Patch Protocol*, which provides a standardized protocol for requesting changes to an OpenRTB bid request or bid response. When processing bid requests and bid responses, the orchestrator will identify the containers that should be included in its processing and will issue a request to each. Participant responses may request in-flight modifications via patch objects that include desired mutations which the orchestrator may accept or reject at their discretion. The OpenRTB Patch Protocol also introduces a concept of *intents* which are declarative attributes that indicate the purpose of a given mutation which enables orchestrators to better determine when and how containers should be invoked . + +![][image2] + +### **Path Clarification** {#path-clarification} + +Sometimes agents might return mutations that need to be inserted at a later point, and may specify a particular id (for example, a particular deal-id). In some cases mutations might be inserted in a different location or sequence than the agent anticipates. To maintain this flexibility on an intention by intention basis \- mutation paths may use semantic references derived from openRTB concepts. Rather than following explicit paths to the overall RTB structure (which the agent may not know), paths identify business level entities rather than specific JSON locations. This allows the spec to express mutations in terms of auction semantics rather than data layout. + +### **Why gRPC and MCP?** {#why-grpc-and-mcp?} + +This spec for agents requires that containers support Remote Procedure Calls for communication between orchestrators and agents. GRPC with protobuf is mandated for all interfaces in this document, while MCP may be used when appropriate. While OpenRTB recently added support for GRPC at a native level, most OpenRTB payloads are still REST and JSON. The reasons for supporting GRPC instead of REST are: + +1) Performance needs to be as fast as possible for these interfaces. GRPC uses protobuf serialization, which is considerably more space and time efficient than JSON. +2) GRPC is easy to validate and has built-in rejection of invalid payloads. +3) Remote Procedure Calls are a better model for agent-based communication than state transitions. + +MCP may also be used (and will likely be required in the future). In particular MCP version 2025-06-18 is the first version that is both performant enough and secure enough to handle significant traffic. This is primarily due to the support of streamable HTTP as well as OAuth authentication. MCP also uses JSON-RPC which makes it a natural successor to REST based interfaces. In addition, while GRPC is ideal for service to service agentic orchestration, MCP can be used not only for service to service orchestration, but also model to agent orchestration, enabling autonomic agentic flows as well. + +In both cases, the same set of interfaces are exposed \- in GRPC via rpc definitions in protobuf, or via tool definitions in MCP. + +# Agent Manifest {#agent-manifest} + +An agent manifest is a standard field in JSON added to the container image via the image metadata expressed as a label which defines how a container is used and managed by an orchestrator. The manifest is specified as a key-value pair with the key being “agent-manifest” and the value being a JSON structure. + +The manifest provides business requirements for the container such as: + +* The name of the agent +* The vendor for the agent +* The owner (email address) of the agent + +It also includes a number of business metadata fields: + +* The intent(s) supported by this agent. + +It also includes information about the runtime configuration for the container: + +* The minimum CPU and memory resources it requires, +* Any dependencies on other services (by name) that the container needs to communicate with. + +# Technical Implementation {#technical-implementation} + +## Infrastructure Considerations {#infrastructure-considerations} + +Composable and deployable mean that Agents need to be able to be deployed in a wide variety of infrastructure in a manner that is consistent with the base infrastructure. In practice this means that they must be structured as OCI containers and must be deployable into a distributed container ecosystem (typically Kubernetes) +The following best practices for Docker images must be followed by both the agent provider and orchestrator for any agent acting on bidstream data: + +* **Mandatory health checks** should be implemented as both liveness and readiness probes to ensure robust self-healing capabilities, allowing infrastructure to operate autonomously and maintain continuous service availability even during partial failures. +* **Image signing and verification** must be enforced across all deployments to establish a chain of trust from development to production, preventing unauthorized or compromised artifacts from entering the bidstream ecosystem. +* **Images must be built by a managed and auditable CI/CD pipeline.** Pipelines should automate the entire release process with integrated security scanning, compliance checks, and deployment validation to maintain velocity while ensuring quality and consistency. +* **Standard logging and monitoring** must be integrated into all deployments to provide real-time visibility into system health, enabling proactive identification of performance bottlenecks and security anomalies before they impact service delivery. Containers must support Open Telemetry endpoints for metrics and support Open Tracing for distributed trace and span collections. + +The following are a few of the best practices for docker images which must be followed for any orchestrator: + +* **Network policies and segmentation** must be implemented to control traffic flows between services, between containers and between privacy sensitive services to protect them from unauthorized workloads. Systems should be configured to provide defense-in-depth isolation that prevents lateral movement and limits the blast radius of security breaches within your infrastructure. +* **Resource quotas and limits** must be defined at both namespace and container levels to guarantee predictable performance, prevent resource starvation, and optimize utilization across your infrastructure. + +## Manifest Requirements {#manifest-requirements} + +Agents are expected to provide a manifest as described above that expresses their needs: + +1) Minimum CPU/Memory requirements +2) The intents they will invoke +3) Other services that they expect to be available (by name) + +If the orchestrating entity cannot support the requirements and intents of a container, it should not be started. + +## Bidstream Integration {#bidstream-integration} + +Agents are implemented as containers that are essentially black boxes that allow service providers to execute core business logic. Each container “lives” in a detached and isolated compute environment once deployed. For these containers to work, they must integrate into the bid stream using predefined intentions. Because participants in the AdTech ecosystem generally have unique mixes of infrastructure, software, and practices. What extension points are supported and how they integrate with agents will likely be custom for each participant. Since it’s not feasible or desirable to have everyone run the same software, instead, a standard protocol is defined that supports generic interactions with extension points. OpenRTB provides robust support for data communication within the bidstream, with appropriate abstractions for vendor-specific extensions, but lacks standardized support for incorporating changes like those containers may want to propagate back to the bid stream as part of a decisioning cycle. To address that shortfall, this standard introduces “OpenRTB Patch,” a protocol for expressing desired changes back into OpenRTB requests and responses. + +OpenRTB Patch is designed to adhere to the following principles: + +* Containers provide desired mutations to the orchestrator as “patches” which describe a delta from the provided request or response and may include some combination of additions, changes, and deletions to specified portions of the request or response. Whether or not a patch is applied is left to the discretion of the orchestrator, which will decide to accept or reject mutations in accordance with its business requirements. Whether and how orchestrators will communicate choices to accept or reject patches to agent providers is left to the parties. +* Each patch is atomic. A mutation must be accepted in whole or rejected in whole. Multiple patches may be independently accepted or rejected \- there are no transactions or any ordering guarantees across mutations. +* Mutations are semantically meaningful: an OpenRTB Patch specifies not only what change is desired, but also why it is requested \- this is specified as the intent. +* OpenRTB patch propagates necessary privacy and signal information into containers. It is the container's responsibility to honor all of the specified behavior in the RTB request. +* OpenRTB patch follows the semantics of the OpenRTB standard, including use of “ext” objects for any nonstandard, extended signaling orchestrators that may wish to make available to agent providers. +* OpenRTB patch facilitates auditability within an orchestrator's environment by logging requested and applied patches. +* Orchestrators should provide standard reports and metrics to judge the acceptance rate of mutations and rejection reasons. + +## Service Naming {#service-naming} + +As mentioned above, containers can provide specific dependencies in the name of service that they have a dependency on, Since this will often map to proprietary services that orchestrators provide, or names of other containers, the specification is simply that the service dependency be a string to signify the dependency for mapping. + +# API Design {#api-design} + +RPC definitions for both gRPC and for MCP require a bit more structure than a traditional REST/JSON service. For purposes of this document, a few key patterns are defined, but the authoritative definition for this is the gRPC definition found in the IABTechLab’s Github Project. +These definitions take the form of Protobuf specifications. In particular, the services and messages must be articulated in .proto files. For MCP use cases, the specification is the same as GRPC, with the obvious exception of a tool definition rather than RPC endpoints for each extension point. + +For the purpose of the comment period, there is an initial protobuf definition. These are subject to change. + +## Example Extension Point {#example-extension-point} + +syntax \= "proto2"; + +package com.iabtechlab.bidstream.mutation.v1; + +import "com/iabtechlab/openrtb/v2.6/openrtb.proto"; + +// service definition +service RTBExtensionPoint { + // GetMutations returns RTBResponse containing mutations to be applied at the predetermined auction lifecycle event + rpc GetMutations (RTBRequest) returns (RTBResponse); +} + +message RTBRequest { + // ENUM as per Programmatic Auction Definition IAB TL doc/spec + required Lifecycle lifecycle \= 1; + + required string id \= 2; + optional Extensions ext \= 3; + + required com.iabtechlab.openrtb.v2.BidRequest bid\_request \= 4; + + optional com.iabtechlab.openrtb.v2.BidResponse bid\_response \= 5; + + required int32 tmax \= 6; + +} + +This forms the backbone of the extension system. Each individual request exposes a single rpc and a single stream mechanism for a given extension point. Right now the spec does anticipate the need to support multiple called endpoints so that the same container could support multiple different service requests. + +### **AgenticRTB Specific Requirements** {#agenticrtb-specific-requirements} + +| Field | Type | Description | +| ----- | ----- | ----- | +| id | string | Extension point Request ID | +| bidRequest.imp | imp message | Impression message (per OpenRTB, with exceptions) | +| bidRequest.site | site message | Site message (per OpenRTB) | +| bidRequest.app | app message | App message (per OpenRTB) | +| bidRequest.device | device message | Device message (per OpenRTB) | +| bidRequest.user | user message | User message (per OpenRTB) | +| bidRequest.regs | reg message | Regulation message (per OpenRTB) | +| bidRequest.source | source message | Source message (per OpenRTB) | +| bidRequest.tmax | integer | Maximum time in milliseconds the exchange allows for mutations to be received including internet latency to avoid timeout | + +### Extension Response {#extension-response} + +A mutation represents a change to an existing request —it modifies the system’s state by adding, removing, or updating records. Extension Provider responses take the form of these mutations, proposing adjustments to bid requests or responses. An example Extension Response gRPC is below. + +`message RTBResponse { // Or RequestPatch` + `string id = 1;` + `repeated mutation mutations = 2;` + `MetaData metadata = 3;` +`}` + +`message Mutation {` + `string intent = 1;` + `string op = 2;` + `string path = 3;` + + `// The structure of value depends on the specified intent.` + `// Reserve 100+ for intent-specific payloads` + `// The structure of value depends on the specified intent.` + `// Reserve 100+ for intent-specific payloads` + `oneof value {` + `// List of string Identifiers` + `IDsPayload ids = 100;` + + `// Adjust properties of a specific deal` + `AdjustDealPayload adjust_deal = 101;` + + `// Adjust the bid price` + `AdjustBidPayload adjust_bid = 102;` + + `// Metrics or telemetry data` + `AddMetricsPayload add_metrics = 103;` + `}` + +} + +`message MetaData {` + `string api_version = 1;` + `string model_version = 2;` +`}` + +## Example \- Audience Segments \- Activating Cohorts {#example---audience-segments---activating-cohorts} + +Below is an example of a response return. Since gRPC is a binary protocol, JSON format is used to express concepts rather than serialization formats. + +`{` + `"intent": "activateSegments",` + `"op": "add",` + `"path": "/user/data/segment",` + `"value": {` + `"IDsPayload": [“18-35-age-segment","soccer-watchers"]` + `}` +`}` + +## Example \- Complex Orchestration {#example---complex-orchestration} + +Endpoints may return multiple mutations. If so, each mutation is evaluated separately. Note that there is no support for transactions across multiple mutations. Any mutation here may be accepted or rejected independently of other mutations. + +`[{` + `"intent": "expireDeals",` + `"op": "remove"` + `"path": "/imp/1",` + `"value": {` + `"IDsPayload": [“deal100","deal200"]` + `}` +`},{` + `"intent": "activateDeals",` + `"op": "add",` + `"path": "/imp/1",` + `"value": {` + `"IDsPayload": [“deal300","deal201"]` + `}` +`},{` + `"intent": "adjustDeals",` + `"op": "replace"` + `"path": "/imp/2/deals/400",` + `"value": {` + `“AdjustDealPayload”: {` + `"bidfloor": 5.00,` + `"wadomain": ["adomain.com"],` + `}` + `},` +`},{` + `"intent": "adjustDeals",` + `"op": "replace",` + `"path": "/imp/1/deals/500",` + `"value": {` + `“AdjustDealPayload”: {` + `"bidfloor": 8.00` + `}` + `},` +`}]` + +## Manifest \- Container.json {#manifest---container.json} + +This is an example of a container JSON. Note that this is associated with the metadata for the docker image for transmission. + +{ + "name": "openrtb-container-suite", + "version": "1.0.0", + "description": "Example container manifest for OpenRTB and Digital Advertising use cases", + "image": { + "repository": "registry.example.com/rtb/auction-container", + "tag": "v1.0.5", + "digest": "sha256:abc123def4567890" + }, + "resources": { + "cpu": "500m", + "memory": "256Mi" + }, + "intents": \[ + { + "name": "bidResponseGeneration", + "description": "Generate bid responses in real time based on request data" + }, + { + "name": "bidValuation", + "description": "Evaluate incoming bid requests and determine bid amount" + }, + { + "name": "bidRequestModification", + "description": "Propose mutations to a bid request prior to auction execution" + }, + { + "name": "auctionOrchestration", + "description": "Route or prioritize bid requests across multiple buyers" + }, + { + "name": "metadataEnhancement", + "description": "Insert or modify auction metadata such as fraud or viewability signals" + }, + { + "name": "dynamicDealCuration", + "description": "Curate deals in real time, optimize margins or enforce dynamic inclusion/exclusion lists" + }, + { + "name": "audienceSegmentation", + "description": "Activate or enrich user cohorts and audience segments" + } + \], + "dependencies": { + "fraudDetectionService": { + "service": "fraud-svc", + "ENV\_VARIABLE": FRAUD\_URL + }, + "audienceService": { + "host": "audience-svc", + "port": 9010 + } + }, + "health": { + "livenessProbe": { + "httpGet": { + "path": "/health/live", + "port": 8080 + }, + "initialDelaySeconds": 10, + "periodSeconds": 5 + }, + "readinessProbe": { + "httpGet": { + "path": "/health/ready", + "port": 8080 + }, + "initialDelaySeconds": 5, + "periodSeconds": 5 + } + }, + "security": { + "runAsNonRoot": true, + "dropCapabilities": \["NET\_ADMIN", "SYS\_PTRACE"\], + "networkPolicies": { + "ingress": \["fraud-svc", "audience-svc"\], + "egress": \["logging-svc"\] + } + }, + "maintainers": \[ + { + "name": "IAB Tech Lab Example Team", + "email": "support@iabtechlab.com" + } + \] +} + +[image1]: + +[image2]: \ No newline at end of file diff --git a/Agentic RTB Framework Version 1.0 for PUBLIC COMMENT.pdf b/Agentic RTB Framework Version 1.0 for PUBLIC COMMENT.pdf new file mode 100644 index 0000000..d647d8b Binary files /dev/null and b/Agentic RTB Framework Version 1.0 for PUBLIC COMMENT.pdf differ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..081b6c4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,237 @@ +# CLAUDE.md - Agentic RTB Framework + +This repository implements an agent service based on the IAB Tech Lab's **Agentic RTB Framework (ARTF) v1.0** specification. The framework enables agent-driven containers to participate in OpenRTB bidstream processing. + +**Current Version:** 0.10.0 + +## Overview + +The Agentic RTB Framework defines a standard for implementing agent services that: +- Operate within a host platform's infrastructure +- Process bidstream data in real-time (sub-millisecond latency requirements) +- Propose mutations to OpenRTB bid requests/responses via the "OpenRTB Patch" protocol +- Communicate via gRPC with protobuf serialization +- Support MCP (Model Context Protocol) for AI agent integration + +## Project Structure + +``` +. +├── CLAUDE.md # This file +├── README.md # Project readme +├── Makefile # Build automation +├── Dockerfile # Container build +├── cmd/agent/ # Main entry point +├── internal/ +│ ├── agent/ # gRPC service implementation +│ ├── mcp/ # MCP interface implementation +│ ├── handlers/ # Mutation handlers +│ ├── health/ # Health check endpoints +│ └── web/ # Web UI +├── pkg/pb/ # Generated protobuf code +├── proto/ # Protobuf definitions +├── docs/ # Documentation +└── samples/ # Sample request payloads +``` + +## Protocol Definition + +### Service: RTBExtensionPoint + +The core gRPC service that agents must implement: + +```protobuf +service RTBExtensionPoint { + rpc GetMutations (RTBRequest) returns (RTBResponse); +} +``` + +### Key Messages + +| Message | Description | +|---------|-------------| +| `RTBRequest` | Contains lifecycle stage, request ID, tmax, bid_request, optional bid_response | +| `RTBResponse` | Returns request ID, list of mutations, and metadata | +| `Mutation` | Defines intent, operation (add/remove/replace), path, and value payload | + +### Supported Intents + +| Intent | Value | Description | +|--------|-------|-------------| +| `ACTIVATE_SEGMENTS` | 1 | Activate user segments by external segment IDs | +| `ACTIVATE_DEALS` | 2 | Activate deals by external deal IDs | +| `SUPPRESS_DEALS` | 3 | Suppress deals by external deal IDs | +| `ADJUST_DEAL_FLOOR` | 4 | Adjust the bid floor of a specific deal | +| `ADJUST_DEAL_MARGIN` | 5 | Adjust the deal margin | +| `BID_SHADE` | 6 | Adjust the bid price | +| `ADD_METRICS` | 7 | Add metrics to an impression | + +### Operations + +| Operation | Value | Description | +|-----------|-------|-------------| +| `OPERATION_ADD` | 1 | Add new data | +| `OPERATION_REMOVE` | 2 | Remove existing data | +| `OPERATION_REPLACE` | 3 | Replace existing data | + +### Payload Types + +- `IDsPayload` - List of string identifiers (for segments, deals) +- `AdjustDealPayload` - Bidfloor and margin adjustments +- `AdjustBidPayload` - Bid price adjustments +- `AddMetricsPayload` - OpenRTB Metric objects + +## Development Commands + +```bash +# Build the agent (includes protobuf generation) +make build + +# Run with all interfaces enabled (gRPC, MCP, Web) +make run-all + +# Run specific interfaces +make run-grpc # gRPC only (port 50051) +make run-mcp # MCP only (port 50052) +make run-web # Web + MCP (ports 8081, 50052) + +# Run tests +make test + +# Build Docker image +make docker-build + +# Run with Docker +docker run -p 50051:50051 -p 50052:50052 -p 8081:8081 artf-agent:latest --enable-grpc --enable-mcp --enable-web +``` + +### Port Configuration + +| Port | Service | Description | +|------|---------|-------------| +| 50051 | gRPC | RTBExtensionPoint service | +| 50052 | MCP | Model Context Protocol (standalone mode) | +| 8080 | Health | Kubernetes health probes | +| 8081 | Web UI | Testing and demo interface (+ MCP at `/mcp` when both enabled) | + +**Note:** When both `--enable-web` and `--enable-mcp` are set, MCP is served on the Web UI port (8081) at `/mcp` instead of its own port. This simplifies load balancer configuration. + +### External URL / Load Balancer Support + +Use `--external-url` to specify an external base URL when deploying behind a load balancer: + +```bash +./artf-agent --enable-grpc --enable-mcp --enable-web \ + --external-url "https://rtb.example.com" +``` + +This rewrites all service URLs to use the external address, ensuring the Web UI's MCP endpoint points to the load balancer URL (e.g., `https://rtb.example.com/mcp`). + +### MCP CORS Support + +The MCP interface includes CORS headers to enable cross-origin requests from web browsers: +- `Access-Control-Allow-Origin: *` +- `Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS` +- `Access-Control-Allow-Headers: Content-Type, Mcp-Session-Id, Last-Event-ID` +- `Access-Control-Expose-Headers: Mcp-Session-Id` + +## Implementation Requirements + +### Container Requirements (from ARTF spec) + +1. **Must run as non-root user** +2. **Must implement Kubernetes health probes**: + - Liveness probe: `GET /health/live` + - Readiness probe: `GET /health/ready` +3. **Must follow least-privilege principle** - drop unnecessary capabilities +4. **Must handle graceful shutdowns** +5. **No external network access** - only communicate with orchestrator services +6. **Must support OpenTelemetry** for metrics and distributed tracing + +### Performance Requirements + +- Sub-millisecond response times expected +- Use efficient languages (Go, Rust, Java recommended) +- gRPC with protobuf for serialization efficiency +- Respect `tmax` timeout from requests + +### Agent Manifest + +Containers must include an `agent-manifest` label in image metadata with: + +```json +{ + "name": "agent-name", + "version": "1.0.0", + "vendor": "vendor-name", + "owner": "owner@example.com", + "resources": { + "cpu": "500m", + "memory": "256Mi" + }, + "intents": ["ACTIVATE_SEGMENTS", "ACTIVATE_DEALS"], + "dependencies": { + "serviceName": { + "service": "svc-name", + "port": 9000 + } + }, + "health": { + "livenessProbe": { "httpGet": { "path": "/health/live", "port": 8080 } }, + "readinessProbe": { "httpGet": { "path": "/health/ready", "port": 8080 } } + } +} +``` + +## Code Style Guidelines + +- Use Go for implementation (efficient, good gRPC support) +- Follow standard Go project layout +- Use `context.Context` for cancellation and timeouts +- Implement proper error handling with gRPC status codes +- Add structured logging with OpenTelemetry integration +- Write unit tests for all mutation handlers + +## Example Mutation Response + +```json +{ + "id": "request-123", + "mutations": [ + { + "intent": "ACTIVATE_SEGMENTS", + "op": "OPERATION_ADD", + "path": "/user/data/segment", + "ids": { + "id": ["18-35-age-segment", "soccer-watchers"] + } + } + ], + "metadata": { + "api_version": "1.0", + "model_version": "v2.1" + } +} +``` + +## Dependencies + +The protobuf imports OpenRTB v2.6 definitions: +```protobuf +import "com/iabtechlab/openrtb/v2.6/openrtb.proto"; +``` + +You'll need the IAB Tech Lab OpenRTB protobuf definitions from: +https://github.com/InteractiveAdvertisingBureau/openrtb + +## References + +- [Agentic RTB Framework Specification](https://iabtechlab.com/standards/artf/) +- [OpenRTB Specification](https://iabtechlab.com/standards/openrtb/) +- [IAB Tech Lab GitHub](https://github.com/InteractiveAdvertisingBureau) +- [gRPC Go Documentation](https://grpc.io/docs/languages/go/) +- [Protocol Buffers](https://protobuf.dev/) + +## License + +The Agentic RTB Framework specification is licensed under Creative Commons Attribution 3.0. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d73ff07 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,95 @@ +# Build stage +FROM golang:1.23 AS builder + +# Set working directory +WORKDIR /app + +# Copy go mod files +COPY go.mod go.sum ./ + +# Download dependencies +RUN go mod download + +# Copy source code +COPY . . + +# Build arguments for versioning +ARG VERSION=0.10.0 + +# Build the binary with version info +RUN CGO_ENABLED=0 GOOS=linux go build \ + -ldflags="-w -s -X main.Version=${VERSION}" \ + -o /artf-agent \ + ./cmd/agent + +# Runtime stage +FROM ubuntu:24.04 + +# Build arguments for agent manifest +ARG VERSION=0.10.0 +ARG AGENT_NAME=artf-reference-agent +ARG AGENT_VENDOR="IAB Tech Lab" +ARG AGENT_OWNER=artf@iabtechlab.com + +# Agent manifest label (ARTF specification requirement) +# This label describes the agent's capabilities and configuration +LABEL agent-manifest="{ \ + \"name\": \"${AGENT_NAME}\", \ + \"version\": \"${VERSION}\", \ + \"vendor\": \"${AGENT_VENDOR}\", \ + \"owner\": \"${AGENT_OWNER}\", \ + \"resources\": { \ + \"cpu\": \"500m\", \ + \"memory\": \"256Mi\" \ + }, \ + \"intents\": [ \ + \"ACTIVATE_SEGMENTS\", \ + \"ACTIVATE_DEALS\", \ + \"SUPPRESS_DEALS\", \ + \"ADJUST_DEAL_FLOOR\", \ + \"ADJUST_DEAL_MARGIN\", \ + \"BID_SHADE\", \ + \"ADD_METRICS\" \ + ], \ + \"health\": { \ + \"livenessProbe\": { \ + \"httpGet\": { \"path\": \"/health/live\", \"port\": 8080 } \ + }, \ + \"readinessProbe\": { \ + \"httpGet\": { \"path\": \"/health/ready\", \"port\": 8080 } \ + } \ + } \ +}" + +# Additional metadata labels +LABEL org.opencontainers.image.title="${AGENT_NAME}" +LABEL org.opencontainers.image.version="${VERSION}" +LABEL org.opencontainers.image.vendor="${AGENT_VENDOR}" +LABEL org.opencontainers.image.description="ARTF Reference Agent - Agentic RTB Framework implementation" +LABEL org.opencontainers.image.source="https://github.com/IABTechLab/agentic-rtb-framework" +LABEL org.opencontainers.image.licenses="AGPL-3.0" + +# Install CA certificates for HTTPS +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + tzdata \ + && rm -rf /var/lib/apt/lists/* + +# Copy the binary +COPY --from=builder /artf-agent /artf-agent + +# Use non-root user (nobody already exists in Ubuntu with UID 65534) +USER nobody + +# Expose ports (gRPC: 50051, Web/MCP: 8081, Health: 8080) +EXPOSE 50051 8081 8080 + +# Health check +HEALTHCHECK --interval=5s --timeout=3s --start-period=5s --retries=3 \ + CMD ["/artf-agent", "-health-check"] || exit 1 + +# Set entrypoint +ENTRYPOINT ["/artf-agent"] + +# Default arguments (enable all interfaces) +CMD ["--enable-grpc", "--enable-mcp", "--enable-web", "--grpc-port=50051", "--web-port=8081", "--health-port=8080"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..be3f7b2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9c888dc --- /dev/null +++ b/Makefile @@ -0,0 +1,307 @@ +.PHONY: all build test clean generate docker run lint help proto-deps fetch-openrtb + +# Go parameters +GOCMD=go +GOBUILD=$(GOCMD) build +GOTEST=$(GOCMD) test +GOGET=$(GOCMD) get +GOMOD=$(GOCMD) mod +BINARY_NAME=artf-agent +DOCKER_IMAGE=artf-agent + +# Build information +VERSION?=0.10.0 +BUILD_TIME=$(shell date -u +"%Y-%m-%dT%H:%M:%SZ") +LDFLAGS=-ldflags "-X main.Version=$(VERSION) -X main.BuildTime=$(BUILD_TIME) -w -s" + +# Agent manifest configuration (customizable via environment or make args) +AGENT_NAME?=artf-reference-agent +AGENT_VENDOR?=IAB Tech Lab +AGENT_OWNER?=artf@iabtechlab.com + +# Default ports +GRPC_PORT?=50051 +MCP_PORT?=50052 +WEB_PORT?=8081 +HEALTH_PORT?=8080 + +# OpenRTB Proto Configuration +OPENRTB_REPO=https://raw.githubusercontent.com/IABTechLab/openrtb-proto-v2/master +OPENRTB_PROTO_SRC=$(OPENRTB_REPO)/openrtb-core/src/main/protobuf/openrtb.proto +OPENRTB_PROTO_DIR=proto/com/iabtechlab/openrtb/v2 +OPENRTB_PROTO_FILE=$(OPENRTB_PROTO_DIR)/openrtb.proto + +all: generate build test + +## help: Show this help message +help: + @echo "ARTF Agent - Makefile commands:" + @echo "" + @echo "Build Commands:" + @echo " make build Build the agent binary" + @echo " make build-all Build with all features enabled" + @echo " make generate Generate protobuf code" + @echo " make fetch-openrtb Download OpenRTB 2.6 proto from IAB Tech Lab" + @echo " make clean Remove build artifacts" + @echo "" + @echo "Run Commands:" + @echo " make run Run with gRPC only (default)" + @echo " make run-grpc Run with gRPC enabled" + @echo " make run-mcp Run with MCP enabled" + @echo " make run-web Run with Web UI enabled" + @echo " make run-all Run with all services enabled" + @echo " make run-dev Run in development mode (all services)" + @echo "" + @echo "Test Commands:" + @echo " make test Run unit tests" + @echo " make test-coverage Run tests with coverage report" + @echo " make grpc-test Test gRPC endpoint" + @echo " make mcp-test Test MCP endpoint" + @echo " make health-check Check health endpoints" + @echo "" + @echo "Docker Commands:" + @echo " make docker-build Build Docker image with agent-manifest" + @echo " make docker-run Run Docker container" + @echo " make docker-run-all Run Docker with all services" + @echo " make docker-inspect-manifest Show agent-manifest label" + @echo " make docker-compose-up Start with docker-compose" + @echo "" + @echo " Custom agent manifest (example):" + @echo " make docker-build AGENT_NAME=my-agent AGENT_VENDOR=\"My Corp\" VERSION=1.0.0" + @echo "" + @echo "Sample Commands:" + @echo " make sample-banner Send sample banner request via MCP" + @echo " make sample-video Send sample video request via MCP" + @echo " make sample-bidshade Send sample bid shading request via MCP" + @echo "" + +## fetch-openrtb: Download OpenRTB 2.6 proto from IAB Tech Lab repository +fetch-openrtb: + @echo "Fetching OpenRTB 2.6 protobuf definition from IAB Tech Lab..." + @mkdir -p $(OPENRTB_PROTO_DIR) + @curl -sSL $(OPENRTB_PROTO_SRC) -o $(OPENRTB_PROTO_FILE).tmp + @echo "Adding go_package option for ARTF compatibility..." + @sed '/^package /a\option go_package = "github.com/iabtechlab/agentic-rtb-framework/pkg/pb/openrtb";' $(OPENRTB_PROTO_FILE).tmp > $(OPENRTB_PROTO_FILE) + @rm -f $(OPENRTB_PROTO_FILE).tmp + @echo "OpenRTB 2.6 proto downloaded to $(OPENRTB_PROTO_FILE)" + +## generate: Generate Go code from protobuf definitions +generate: fetch-openrtb + @echo "Generating protobuf code..." + @./scripts/generate.sh + +## build: Build the agent binary +build: + @echo "Building $(BINARY_NAME) v$(VERSION)..." + $(GOBUILD) $(LDFLAGS) -o $(BINARY_NAME) ./cmd/agent + +## build-all: Build with embedded static files +build-all: generate build + +## test: Run unit tests +test: + @echo "Running tests..." + $(GOTEST) -v -race -cover ./... + +## test-coverage: Run tests with coverage report +test-coverage: + @echo "Running tests with coverage..." + $(GOTEST) -v -race -coverprofile=coverage.out ./... + $(GOCMD) tool cover -html=coverage.out -o coverage.html + @echo "Coverage report generated: coverage.html" + +## lint: Run linter +lint: + @echo "Running linter..." + golangci-lint run ./... + +## clean: Remove build artifacts +clean: + @echo "Cleaning..." + rm -f $(BINARY_NAME) + rm -f coverage.out coverage.html + rm -rf pkg/pb/artf/*.pb.go + rm -rf pkg/pb/openrtb/*.pb.go + +# +# Run Commands +# + +## run: Run the agent with default settings (gRPC only) +run: build + @echo "Starting agent (gRPC only)..." + ./$(BINARY_NAME) --enable-grpc --grpc-port=$(GRPC_PORT) --health-port=$(HEALTH_PORT) + +## run-grpc: Run with gRPC enabled +run-grpc: build + @echo "Starting agent with gRPC..." + ./$(BINARY_NAME) --enable-grpc --grpc-port=$(GRPC_PORT) --health-port=$(HEALTH_PORT) + +## run-mcp: Run with MCP enabled +run-mcp: build + @echo "Starting agent with MCP..." + ./$(BINARY_NAME) --enable-grpc=false --enable-mcp --mcp-port=$(MCP_PORT) --health-port=$(HEALTH_PORT) + +## run-web: Run with Web UI enabled (requires MCP) +run-web: build + @echo "Starting agent with Web UI and MCP..." + ./$(BINARY_NAME) --enable-grpc=false --enable-mcp --enable-web --mcp-port=$(MCP_PORT) --web-port=$(WEB_PORT) --health-port=$(HEALTH_PORT) + +## run-all: Run with all services enabled +run-all: build + @echo "Starting agent with all services..." + ./$(BINARY_NAME) --enable-grpc --enable-mcp --enable-web \ + --grpc-port=$(GRPC_PORT) --mcp-port=$(MCP_PORT) --web-port=$(WEB_PORT) --health-port=$(HEALTH_PORT) + +## run-dev: Run in development mode (all services, verbose) +run-dev: build + @echo "Starting agent in development mode..." + @echo "Services:" + @echo " gRPC: localhost:$(GRPC_PORT)" + @echo " MCP: http://localhost:$(MCP_PORT)/mcp" + @echo " Web UI: http://localhost:$(WEB_PORT)/" + @echo " Health: http://localhost:$(HEALTH_PORT)/health/ready" + @echo "" + ./$(BINARY_NAME) --enable-grpc --enable-mcp --enable-web \ + --grpc-port=$(GRPC_PORT) --mcp-port=$(MCP_PORT) --web-port=$(WEB_PORT) --health-port=$(HEALTH_PORT) + +# +# Docker Commands +# + +## docker-build: Build Docker image with agent-manifest label +docker-build: + @echo "Building Docker image..." + @echo " Agent: $(AGENT_NAME) v$(VERSION)" + @echo " Vendor: $(AGENT_VENDOR)" + docker build \ + --build-arg VERSION=$(VERSION) \ + --build-arg AGENT_NAME="$(AGENT_NAME)" \ + --build-arg AGENT_VENDOR="$(AGENT_VENDOR)" \ + --build-arg AGENT_OWNER="$(AGENT_OWNER)" \ + -t $(DOCKER_IMAGE):$(VERSION) \ + -t $(DOCKER_IMAGE):latest . + +## docker-run: Run Docker container with gRPC +docker-run: + @echo "Running Docker container (gRPC only)..." + docker run -p $(GRPC_PORT):50051 -p $(HEALTH_PORT):8080 \ + $(DOCKER_IMAGE):latest --enable-grpc + +## docker-run-all: Run Docker container with all services +docker-run-all: + @echo "Running Docker container (all services)..." + docker run -p $(GRPC_PORT):50051 -p $(MCP_PORT):50052 -p $(WEB_PORT):8081 -p $(HEALTH_PORT):8080 \ + $(DOCKER_IMAGE):latest --enable-grpc --enable-mcp --enable-web + +## docker-compose-up: Start with docker-compose +docker-compose-up: + docker-compose up --build + +## docker-compose-down: Stop docker-compose services +docker-compose-down: + docker-compose down + +## docker-inspect-manifest: Show the agent-manifest label from the Docker image +docker-inspect-manifest: + @echo "Agent Manifest for $(DOCKER_IMAGE):$(VERSION):" + @docker inspect $(DOCKER_IMAGE):$(VERSION) --format '{{index .Config.Labels "agent-manifest"}}' | python3 -m json.tool 2>/dev/null || \ + docker inspect $(DOCKER_IMAGE):$(VERSION) --format '{{index .Config.Labels "agent-manifest"}}' + +# +# Dependency Commands +# + +## deps: Download dependencies +deps: + @echo "Downloading dependencies..." + $(GOMOD) download + $(GOMOD) tidy + +## proto-deps: Install protobuf tooling +proto-deps: + @echo "Installing protobuf tools..." + $(GOGET) google.golang.org/protobuf/cmd/protoc-gen-go@latest + $(GOGET) google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest + +# +# Test Commands +# + +## grpc-test: Test gRPC endpoint with grpcurl +grpc-test: + @echo "Testing gRPC endpoint..." + grpcurl -plaintext localhost:$(GRPC_PORT) list + grpcurl -plaintext localhost:$(GRPC_PORT) describe com.iabtechlab.bidstream.mutation.v1.RTBExtensionPoint + +## mcp-test: Test MCP endpoint initialization +mcp-test: + @echo "Testing MCP endpoint..." + @curl -s -X POST http://localhost:$(MCP_PORT)/mcp \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | jq . + +## mcp-tools: List available MCP tools +mcp-tools: + @echo "Listing MCP tools..." + @curl -s -X POST http://localhost:$(MCP_PORT)/mcp \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' | jq . + +## health-check: Check health endpoints +health-check: + @echo "Checking health endpoints..." + @echo "=== Liveness ===" + @curl -s http://localhost:$(HEALTH_PORT)/health/live | jq . + @echo "=== Readiness ===" + @curl -s http://localhost:$(HEALTH_PORT)/health/ready | jq . + @echo "=== Info ===" + @curl -s http://localhost:$(HEALTH_PORT)/health/info | jq . + +# +# Sample Request Commands +# + +## sample-banner: Send sample banner request via MCP +sample-banner: + @echo "Sending sample banner request..." + @curl -s -X POST http://localhost:$(MCP_PORT)/mcp \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"extend_rtb","arguments":'"$$(cat samples/banner-basic.json)"'}}' | jq . + +## sample-video: Send sample video request via MCP +sample-video: + @echo "Sending sample video request..." + @curl -s -X POST http://localhost:$(MCP_PORT)/mcp \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"extend_rtb","arguments":'"$$(cat samples/video-deals.json)"'}}' | jq . + +## sample-bidshade: Send sample bid shading request via MCP +sample-bidshade: + @echo "Sending sample bid shading request..." + @curl -s -X POST http://localhost:$(MCP_PORT)/mcp \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"extend_rtb","arguments":'"$$(cat samples/bid-shading.json)"'}}' | jq . + +## sample-native: Send sample native ad request via MCP +sample-native: + @echo "Sending sample native ad request..." + @curl -s -X POST http://localhost:$(MCP_PORT)/mcp \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"extend_rtb","arguments":'"$$(cat samples/native-ad.json)"'}}' | jq . + +## sample-multi: Send sample multi-impression request via MCP +sample-multi: + @echo "Sending sample multi-impression request..." + @curl -s -X POST http://localhost:$(MCP_PORT)/mcp \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"extend_rtb","arguments":'"$$(cat samples/multi-impression.json)"'}}' | jq . + +# +# Version Info +# + +## version: Show version information +version: + @echo "ARTF Agent v$(VERSION)" + @echo "Build time: $(BUILD_TIME)" diff --git a/README.md b/README.md index b675f82..4c89a15 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,288 @@ https://iabtechlab.com/standards/artf/ #### Contact For more information, or to get involved, please email support@iabtechlab.com. -#### About IAB Tech Lab +--- + +## Go Reference Implementation + +A Go implementation of the IAB Tech Lab's **Agentic RTB Framework (ARTF) v1.0** specification for agent-driven containers in OpenRTB and Digital Advertising. + +### Overview + +This project implements a multi-protocol server that conforms to the ARTF specification, enabling: + +- **Segment Activation** - Activate user segments based on bid request data +- **Deal Management** - Activate, suppress, and adjust deals dynamically +- **Bid Shading** - Optimize bid prices using intelligent pricing strategies +- **Metrics Addition** - Add viewability and other metrics to impressions + +### Supported Protocols + +| Protocol | Port | Description | +|----------|------|-------------| +| gRPC | 50051 | Native RTBExtensionPoint service | +| MCP | 50052* | Model Context Protocol for AI agents | +| Web UI | 8081 | Browser-based testing interface | +| Health | 8080 | Kubernetes liveness/readiness probes | + +*When both Web UI and MCP are enabled, MCP is served on the Web UI port (8081) at `/mcp` for simplified load balancer configuration. + +### Quick Start + +#### Prerequisites + +- Go 1.23+ +- Protocol Buffers compiler (`protoc`) v3.21+ +- Docker (optional, for containerized deployment) + +#### Critical Dependencies + +The following tools must be installed to generate protobuf code: + +| Tool | Version | Installation | +|------|---------|--------------| +| `protoc` | 3.21+ | System package manager or [releases](https://github.com/protocolbuffers/protobuf/releases) | +| `protoc-gen-go` | 1.34+ | `go install google.golang.org/protobuf/cmd/protoc-gen-go@latest` | +| `protoc-gen-go-grpc` | 1.4+ | `go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest` | + +**Important:** Ensure `$(go env GOPATH)/bin` is in your `PATH`: +```bash +export PATH="$PATH:$(go env GOPATH)/bin" +``` + +Go module dependencies (managed via `go.mod`): + +| Package | Version | Purpose | +|---------|---------|---------| +| `google.golang.org/grpc` | 1.64.0 | gRPC framework | +| `google.golang.org/protobuf` | 1.34.1 | Protocol Buffers runtime | +| `github.com/mark3labs/mcp-go` | 0.43.1 | Model Context Protocol server | + +#### Build and Run + +```bash +# Install dependencies +make deps + +# Generate protobuf code +make generate + +# Build the server +make build + +# Run with all services enabled +make run-all + +# Run in development mode (verbose) +make run-dev +``` + +#### Docker Deployment + +```bash +# Build Docker image +make docker-build + +# Run with Docker +make docker-run-all + +# Or use docker-compose +make docker-compose-up +``` + +### Architecture + +``` +. +├── cmd/agent/ # Main agent entry point +├── internal/ +│ ├── agent/ # gRPC agent implementation +│ ├── handlers/ # Mutation handlers for different intents +│ ├── health/ # Kubernetes health check endpoints +│ ├── mcp/ # MCP server implementation +│ └── web/ # Web UI for testing +├── pkg/pb/ # Generated protobuf Go code +├── proto/ # Protocol buffer definitions +│ ├── agenticrtbframework.proto # ARTF service definition +│ └── com/iabtechlab/openrtb/ # OpenRTB v2.6 definitions +├── samples/ # Sample ORTB payloads for testing +├── docs/ # Specifications and documentation +├── scripts/ # Build and utility scripts +├── Dockerfile # Container build definition +└── docker-compose.yml # Local development setup +``` + +### API + +#### RTBExtensionPoint Service (gRPC) + +```protobuf +service RTBExtensionPoint { + rpc GetMutations (RTBRequest) returns (RTBResponse); +} +``` + +#### MCP Tool: extend_rtb + +The MCP server exposes an `extend_rtb` tool that accepts OpenRTB bid requests and returns proposed mutations. + +### Supported Intents + +| Intent | Description | +|--------|-------------| +| `ACTIVATE_SEGMENTS` | Activate user segments by external segment IDs | +| `ACTIVATE_DEALS` | Activate deals by external deal IDs | +| `SUPPRESS_DEALS` | Suppress deals by external deal IDs | +| `ADJUST_DEAL_FLOOR` | Adjust the bid floor of a specific deal | +| `ADJUST_DEAL_MARGIN` | Adjust the deal margin | +| `BID_SHADE` | Adjust the bid price | +| `ADD_METRICS` | Add metrics to an impression | + +### Configuration + +| Flag | Default | Description | +|------|---------|-------------| +| `--listen` | "" | Bind address for all services (default: all interfaces) | +| `--external-url` | "" | External base URL for load balancer (rewrites all service URLs) | +| `--enable-grpc` | true | Enable gRPC server | +| `--enable-mcp` | false | Enable MCP server | +| `--enable-web` | false | Enable web interface | +| `--grpc-port` | 50051 | gRPC server port | +| `--mcp-port` | 50052 | MCP server port (ignored when both Web and MCP enabled) | +| `--web-port` | 8081 | Web interface port | +| `--health-port` | 8080 | Health check HTTP port | + +#### Load Balancer Configuration + +When deploying behind a load balancer, use `--external-url` to ensure all generated URLs point to the external address: + +```bash +./artf-agent --enable-grpc --enable-mcp --enable-web \ + --external-url "https://rtb.example.com" +``` + +This configures the agent so that: +- Web UI and MCP are served on the same port (8081) +- The MCP endpoint URL shown in the Web UI will be `https://rtb.example.com/mcp` +- All health and service URLs use the external base URL + +### Testing + +```bash +# Run unit tests +make test + +# Run with coverage +make test-coverage + +# Test gRPC endpoint (requires grpcurl) +make grpc-test + +# Test MCP endpoint +make mcp-test + +# Check health endpoints +make health-check + +# Send sample requests via MCP +make sample-banner +make sample-video +make sample-bidshade +``` + +### Security + +This implementation follows ARTF security requirements: + +- Runs as non-root user +- Drops unnecessary capabilities +- Read-only filesystem +- No external network access (configurable) +- Health probes for Kubernetes integration + +### Documentation + +- [Implementation Specification](docs/00-EXAMPLE.md) +- [MCP Integration Guide](docs/01-MCP.md) + +### AI Assistant Integration (MCP) + +The ARTF agent can be added as an MCP server to AI assistants that support the Model Context Protocol, giving them access to the `extend_rtb` tool for processing OpenRTB bid requests. + +#### Claude Desktop + +Add to your Claude Desktop configuration file: + +**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` +**Windows:** `%APPDATA%\Claude\claude_desktop_config.json` +**Linux:** `~/.config/Claude/claude_desktop_config.json` + +```json +{ + "mcpServers": { + "artf": { + "command": "/path/to/artf-agent", + "args": ["--enable-mcp", "--enable-grpc=false"], + "env": {} + } + } +} +``` + +After saving, restart Claude Desktop. The `extend_rtb` tool will be available for RTB mutation requests. + +#### Claude Code (CLI) + +Create a `.mcp.json` file in your project root or home directory: + +```json +{ + "mcpServers": { + "artf": { + "command": "/path/to/artf-agent", + "args": ["--enable-mcp", "--enable-grpc=false"], + "env": {} + } + } +} +``` + +Or add to your existing Claude Code settings. + +#### Remote MCP Server (HTTP) + +For remote deployments, run the agent with MCP enabled and connect via HTTP: + +```bash +# Start the agent with MCP over HTTP +./artf-agent --enable-mcp --enable-web --web-port=8081 + +# MCP endpoint will be available at: http://localhost:8081/mcp +``` + +For Claude Desktop with a remote server, use an MCP proxy or configure your client to connect to the HTTP endpoint. + +#### ChatGPT + +ChatGPT does not natively support MCP. However, you can: + +1. **Use the Web UI** - Access the built-in testing interface at `http://localhost:8081` +2. **Custom GPT with Actions** - Create a Custom GPT that calls the MCP HTTP endpoint via Actions (requires exposing the endpoint publicly) +3. **API Integration** - Use the gRPC or MCP HTTP API directly in your application + +#### Available MCP Tool + +Once connected, the following tool is available: + +| Tool | Description | +|------|-------------| +| `extend_rtb` | Process OpenRTB bid request/response and return proposed mutations | + +Example prompt: *"Use extend_rtb to activate segments for a user born in 1990 viewing a sports website"* + +--- + +#### About IAB Tech Lab The IAB Technology Laboratory is a nonprofit research and development consortium charged with producing and helping companies implement global industry technical standards and solutions. The goal of the Tech Lab is to reduce friction associated with the digital advertising @@ -22,10 +303,13 @@ in the digital advertising supply chain. Learn more about IAB Tech Lab here: [https://www.iabtechlab.com/](https://www.iabtechlab.com/) ### License -ARTF Specification the IAB Tech Lab is licensed under a Creative Commons Attribution 3.0 License. To view a copy of this license, visit creativecommons.org/licenses/by/3.0/ or write to Creative Commons, 171 Second Street, Suite 300, San Francisco, CA 94105, USA. -By submitting an idea, specification, software code, document, file, or other material (each, a “Submission”) to the ARTF repository, or to the IAB Tech Lab in relation to ARTF you agree to and hereby license such Submission to the IAB Tech Lab under the Creative Commons Attribution 3.0 License and agree that such Submission may be used and made available to the public under the terms of such license.  If you are a member of the IAB Tech Lab then the terms and conditions of the [IPR Policy](https://iabtechlab.com/ipr-iab-techlab/acknowledge-ipr/) may also be applicable to your Submission, and if the IPR Policy is applicable to your Submission then the IPR Policy will control  in the event of a conflict between the Creative Commons Attribution 3.0 License and the IPR Policy. +**Reference Implementation (Source Code):** The Go reference implementation in this repository is Copyright (c) 2025 Index Exchange Inc. and is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). See the [LICENSE](LICENSE) file for details. + +**ARTF Specification:** The IAB Tech Lab ARTF Specification is licensed under a Creative Commons Attribution 3.0 License. To view a copy of this license, visit creativecommons.org/licenses/by/3.0/ or write to Creative Commons, 171 Second Street, Suite 300, San Francisco, CA 94105, USA. + +By submitting an idea, specification, software code, document, file, or other material (each, a "Submission") to the ARTF repository, or to the IAB Tech Lab in relation to ARTF you agree to and hereby license such Submission to the IAB Tech Lab under the Creative Commons Attribution 3.0 License and agree that such Submission may be used and made available to the public under the terms of such license. If you are a member of the IAB Tech Lab then the terms and conditions of the [IPR Policy](https://iabtechlab.com/ipr-iab-techlab/acknowledge-ipr/) may also be applicable to your Submission, and if the IPR Policy is applicable to your Submission then the IPR Policy will control in the event of a conflict between the Creative Commons Attribution 3.0 License and the IPR Policy. #### Disclaimer -THE STANDARDS, THE SPECIFICATIONS, THE MEASUREMENT GUIDELINES, AND ANY OTHER MATERIALS OR SERVICES PROVIDED TO OR USED BY YOU HEREUNDER (THE “PRODUCTS AND SERVICES”) ARE PROVIDED “AS IS” AND “AS AVAILABLE,” AND IAB TECHNOLOGY LABORATORY, INC. (“TECH LAB”) MAKES NO WARRANTY WITH RESPECT TO THE SAME AND HEREBY DISCLAIMS ANY AND ALL EXPRESS, IMPLIED, OR STATUTORY WARRANTIES, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AVAILABILITY, ERROR-FREE OR UNINTERRUPTED OPERATION, AND ANY WARRANTIES ARISING FROM A COURSE OF DEALING, COURSE OF PERFORMANCE, OR USAGE OF TRADE. TO THE EXTENT THAT TECH LAB MAY NOT AS A MATTER OF APPLICABLE LAW DISCLAIM ANY IMPLIED WARRANTY, THE SCOPE AND DURATION OF SUCH WARRANTY WILL BE THE MINIMUM PERMITTED UNDER SUCH LAW. THE PRODUCTS AND SERVICES DO NOT CONSTITUTE BUSINESS OR LEGAL ADVICE. TECH LAB DOES NOT WARRANT THAT THE PRODUCTS AND SERVICES PROVIDED TO OR USED BY YOU HEREUNDER SHALL CAUSE YOU AND/OR YOUR PRODUCTS OR SERVICES TO BE IN COMPLIANCE WITH ANY APPLICABLE LAWS, REGULATIONS, OR SELF-REGULATORY FRAMEWORKS, AND YOU ARE SOLELY RESPONSIBLE FOR COMPLIANCE WITH THE SAME, INCLUDING, BUT NOT LIMITED TO, DATA PROTECTION LAWS, SUCH AS THE PERSONAL INFORMATION PROTECTION AND ELECTRONIC DOCUMENTS ACT (CANADA), THE DATA PROTECTION DIRECTIVE (EU), THE E-PRIVACY DIRECTIVE (EU), THE GENERAL DATA PROTECTION REGULATION (EU), AND THE E-PRIVACY REGULATION (EU) AS AND WHEN THEY BECOME EFFECTIVE. +THE STANDARDS, THE SPECIFICATIONS, THE MEASUREMENT GUIDELINES, AND ANY OTHER MATERIALS OR SERVICES PROVIDED TO OR USED BY YOU HEREUNDER (THE "PRODUCTS AND SERVICES") ARE PROVIDED "AS IS" AND "AS AVAILABLE," AND IAB TECHNOLOGY LABORATORY, INC. ("TECH LAB") MAKES NO WARRANTY WITH RESPECT TO THE SAME AND HEREBY DISCLAIMS ANY AND ALL EXPRESS, IMPLIED, OR STATUTORY WARRANTIES, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AVAILABILITY, ERROR-FREE OR UNINTERRUPTED OPERATION, AND ANY WARRANTIES ARISING FROM A COURSE OF DEALING, COURSE OF PERFORMANCE, OR USAGE OF TRADE. TO THE EXTENT THAT TECH LAB MAY NOT AS A MATTER OF APPLICABLE LAW DISCLAIM ANY IMPLIED WARRANTY, THE SCOPE AND DURATION OF SUCH WARRANTY WILL BE THE MINIMUM PERMITTED UNDER SUCH LAW. THE PRODUCTS AND SERVICES DO NOT CONSTITUTE BUSINESS OR LEGAL ADVICE. TECH LAB DOES NOT WARRANT THAT THE PRODUCTS AND SERVICES PROVIDED TO OR USED BY YOU HEREUNDER SHALL CAUSE YOU AND/OR YOUR PRODUCTS OR SERVICES TO BE IN COMPLIANCE WITH ANY APPLICABLE LAWS, REGULATIONS, OR SELF-REGULATORY FRAMEWORKS, AND YOU ARE SOLELY RESPONSIBLE FOR COMPLIANCE WITH THE SAME, INCLUDING, BUT NOT LIMITED TO, DATA PROTECTION LAWS, SUCH AS THE PERSONAL INFORMATION PROTECTION AND ELECTRONIC DOCUMENTS ACT (CANADA), THE DATA PROTECTION DIRECTIVE (EU), THE E-PRIVACY DIRECTIVE (EU), THE GENERAL DATA PROTECTION REGULATION (EU), AND THE E-PRIVACY REGULATION (EU) AS AND WHEN THEY BECOME EFFECTIVE. diff --git a/cmd/agent/main.go b/cmd/agent/main.go new file mode 100644 index 0000000..d1ae466 --- /dev/null +++ b/cmd/agent/main.go @@ -0,0 +1,368 @@ +// Copyright (c) 2025 Index Exchange Inc. +// +// This file is part of the Agentic RTB Framework reference implementation. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Package main implements the ARTF agent with gRPC, MCP, and Web interfaces +package main + +import ( + "context" + "flag" + "fmt" + "log" + "net" + "net/http" + "net/url" + "os" + "os/signal" + "strings" + "syscall" + "time" + + "github.com/iabtechlab/agentic-rtb-framework/internal/agent" + "github.com/iabtechlab/agentic-rtb-framework/internal/handlers" + "github.com/iabtechlab/agentic-rtb-framework/internal/health" + "github.com/iabtechlab/agentic-rtb-framework/internal/mcp" + "github.com/iabtechlab/agentic-rtb-framework/internal/web" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" +) + +// Version information (set at build time) +var ( + Version = "dev" + BuildTime = "unknown" +) + +// Command-line flags +var ( + // Feature flags + enableGRPC = flag.Bool("enable-grpc", true, "Enable gRPC interface") + enableMCP = flag.Bool("enable-mcp", false, "Enable MCP interface") + enableWeb = flag.Bool("enable-web", false, "Enable web interface") + + // Bind address for all services (use 0.0.0.0 to bind to all interfaces, 127.0.0.1 for localhost only) + listenAddr = flag.String("listen", "", "Bind address for all services (default: all interfaces)") + + // External URL for load balancer scenarios (e.g., https://api.example.com) + externalURL = flag.String("external-url", "", "External base URL for load balancer (rewrites all service URLs)") + + // Port configuration (optional, with defaults) + grpcPort = flag.Int("grpc-port", 50051, "gRPC port") + mcpPort = flag.Int("mcp-port", 50052, "MCP port") + webPort = flag.Int("web-port", 8081, "Web interface port") + healthPort = flag.Int("health-port", 8080, "Health check HTTP port") + + // Version flag + showVersion = flag.Bool("version", false, "Show version information") +) + +func main() { + flag.Parse() + + // Show version and exit + if *showVersion { + fmt.Printf("ARTF Agent v%s (built: %s)\n", Version, BuildTime) + os.Exit(0) + } + + log.Printf("Starting ARTF Agent v%s", Version) + log.Printf("Features: gRPC=%v, MCP=%v, Web=%v", *enableGRPC, *enableMCP, *enableWeb) + + // Validate that at least one service is enabled + if !*enableGRPC && !*enableMCP && !*enableWeb { + log.Fatal("At least one service must be enabled (--enable-grpc, --enable-mcp, or --enable-web)") + } + + // Create shared components + healthChecker := health.NewChecker() + mutationHandlers := handlers.NewMutationHandlers() + + // Track services for shutdown + var grpcServer *grpc.Server + var mcpAgent *mcp.Agent + var webServer *http.Server + var healthServer *http.Server + + // Start gRPC interface + if *enableGRPC { + grpcServer = grpc.NewServer( + grpc.UnaryInterceptor(agent.LoggingInterceptor), + ) + + artfAgent := agent.NewARTFAgent(mutationHandlers) + agent.RegisterRTBExtensionPointServer(grpcServer, artfAgent) + reflection.Register(grpcServer) + + grpcListenAddr := fmt.Sprintf("%s:%d", *listenAddr, *grpcPort) + grpcListener, err := net.Listen("tcp", grpcListenAddr) + if err != nil { + log.Fatalf("Failed to listen on gRPC address %s: %v", grpcListenAddr, err) + } + + go func() { + log.Printf("gRPC interface listening on %s", grpcListenAddr) + if err := grpcServer.Serve(grpcListener); err != nil { + log.Printf("gRPC error: %v", err) + } + }() + } + + // When both Web and MCP are enabled, serve them on the same port (web port) + // This allows an external load balancer to route to a single endpoint + if *enableWeb && *enableMCP { + // Create MCP agent + mcpAgent = mcp.NewAgent(mutationHandlers, *listenAddr, *webPort) + + // MCP endpoint is relative when served on same port + mcpEndpoint := buildMCPEndpoint() + + webHandler, err := web.NewHandler(mcpEndpoint) + if err != nil { + log.Fatalf("Failed to create web handler: %v", err) + } + + // Create unified mux with both Web and MCP routes + webMux := http.NewServeMux() + webHandler.RegisterRoutes(webMux) + // Mount MCP handler at /mcp path + webMux.Handle("/mcp", mcpAgent.Handler()) + + webListenAddr := fmt.Sprintf("%s:%d", *listenAddr, *webPort) + webServer = &http.Server{ + Addr: webListenAddr, + Handler: webMux, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + } + + go func() { + log.Printf("Web + MCP interface listening on %s", webListenAddr) + if err := webServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Printf("Web + MCP interface error: %v", err) + } + }() + } else { + // Start MCP interface standalone + if *enableMCP { + mcpAgent = mcp.NewAgent(mutationHandlers, *listenAddr, *mcpPort) + mcpListenAddr := fmt.Sprintf("%s:%d", *listenAddr, *mcpPort) + + go func() { + log.Printf("MCP interface listening on %s", mcpListenAddr) + if err := mcpAgent.Start(); err != nil { + log.Printf("MCP error: %v", err) + } + }() + } + + // Start Web interface standalone + if *enableWeb { + mcpEndpoint := "/mcp-disabled" + + webHandler, err := web.NewHandler(mcpEndpoint) + if err != nil { + log.Fatalf("Failed to create web handler: %v", err) + } + + webMux := http.NewServeMux() + webHandler.RegisterRoutes(webMux) + + webListenAddr := fmt.Sprintf("%s:%d", *listenAddr, *webPort) + webServer = &http.Server{ + Addr: webListenAddr, + Handler: webMux, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + } + + go func() { + log.Printf("Web interface listening on %s", webListenAddr) + if err := webServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Printf("Web interface error: %v", err) + } + }() + } + } + + // Start health check HTTP endpoint (always enabled) + healthMux := http.NewServeMux() + healthMux.HandleFunc("/health/live", healthChecker.LivenessHandler) + healthMux.HandleFunc("/health/ready", healthChecker.ReadinessHandler) + healthMux.HandleFunc("/health/info", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + externalURLVal := "" + if *externalURL != "" { + externalURLVal = *externalURL + } + fmt.Fprintf(w, `{"version":"%s","build_time":"%s","grpc":%v,"mcp":%v,"web":%v,"external_url":"%s"}`, + Version, BuildTime, *enableGRPC, *enableMCP, *enableWeb, externalURLVal) + }) + + healthListenAddr := fmt.Sprintf("%s:%d", *listenAddr, *healthPort) + healthServer = &http.Server{ + Addr: healthListenAddr, + Handler: healthMux, + ReadTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, + } + + go func() { + log.Printf("Health check endpoint listening on %s", healthListenAddr) + if err := healthServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Printf("Health check error: %v", err) + } + }() + + // Mark agent as ready + healthChecker.SetReady(true) + log.Printf("Agent is ready to accept requests") + + // Print service URLs + printServiceURLs() + + // Wait for shutdown signal + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + <-sigChan + + log.Printf("Shutting down agent...") + + // Mark as not ready during shutdown + healthChecker.SetReady(false) + + // Graceful shutdown with timeout + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + // Shutdown services + if grpcServer != nil { + grpcServer.GracefulStop() + log.Printf("gRPC interface stopped") + } + + if webServer != nil { + if err := webServer.Shutdown(ctx); err != nil { + log.Printf("Web interface shutdown error: %v", err) + } else { + log.Printf("Web interface stopped") + } + } + + if err := healthServer.Shutdown(ctx); err != nil { + log.Printf("Health endpoint shutdown error: %v", err) + } + + log.Printf("Agent stopped") +} + +// printServiceURLs prints the URLs for enabled services +func printServiceURLs() { + log.Println("=== Service URLs ===") + + if *externalURL != "" { + log.Printf(" External URL: %s", *externalURL) + } + + if *enableGRPC { + if *externalURL != "" { + log.Printf(" gRPC: %s (external)", buildExternalGRPCAddr()) + } else { + log.Printf(" gRPC: %s:%d", formatDisplayAddr(*listenAddr), *grpcPort) + } + } + + // When both Web and MCP are enabled, they share the same port + if *enableWeb && *enableMCP { + log.Printf(" Web+MCP: %s", buildWebEndpoint()) + log.Printf(" └─ MCP: %s", buildMCPEndpoint()) + } else { + if *enableMCP { + log.Printf(" MCP: %s", buildMCPEndpoint()) + } + if *enableWeb { + log.Printf(" Web UI: %s", buildWebEndpoint()) + } + } + + log.Printf(" Health: %s", buildHealthEndpoint()) + log.Println("====================") +} + +// formatDisplayAddr returns a display-friendly address (localhost if empty) +func formatDisplayAddr(addr string) string { + if addr == "" || addr == "0.0.0.0" { + return "localhost" + } + return addr +} + +// buildMCPEndpoint returns the MCP endpoint URL, using external URL if configured. +// When both web and MCP are enabled, MCP is served on the web port. +func buildMCPEndpoint() string { + if *externalURL != "" { + return buildExternalURL("/mcp") + } + // When both web and MCP are enabled, they share the web port + port := *mcpPort + if *enableWeb && *enableMCP { + port = *webPort + } + return fmt.Sprintf("http://%s:%d/mcp", formatDisplayAddr(*listenAddr), port) +} + +// buildWebEndpoint returns the Web UI endpoint URL, using external URL if configured +func buildWebEndpoint() string { + if *externalURL != "" { + return buildExternalURL("/") + } + return fmt.Sprintf("http://%s:%d/", formatDisplayAddr(*listenAddr), *webPort) +} + +// buildHealthEndpoint returns the health check endpoint URL, using external URL if configured +func buildHealthEndpoint() string { + if *externalURL != "" { + return buildExternalURL("/health/ready") + } + return fmt.Sprintf("http://%s:%d/health/ready", formatDisplayAddr(*listenAddr), *healthPort) +} + +// buildExternalGRPCAddr returns the external gRPC address +func buildExternalGRPCAddr() string { + if *externalURL != "" { + parsed, err := url.Parse(*externalURL) + if err == nil { + // For gRPC, we typically use just the host (without path) + // The port might be different or handled by the load balancer + host := parsed.Host + if !strings.Contains(host, ":") { + // Add default gRPC port if not specified + return fmt.Sprintf("%s:%d", host, *grpcPort) + } + return host + } + } + return fmt.Sprintf("%s:%d", formatDisplayAddr(*listenAddr), *grpcPort) +} + +// buildExternalURL constructs a URL using the external base URL and the given path +func buildExternalURL(path string) string { + baseURL := strings.TrimSuffix(*externalURL, "/") + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + return baseURL + path +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..8438cee --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,105 @@ +version: '3.8' + +services: + artf-agent: + build: + context: . + dockerfile: Dockerfile + ports: + - "50051:50051" # gRPC + - "50052:50052" # MCP + - "8081:8081" # Web UI + - "8080:8080" # Health checks + environment: + - GRPC_PORT=50051 + - MCP_PORT=50052 + - WEB_PORT=8081 + - HEALTH_PORT=8080 + command: + - "--enable-grpc" + - "--enable-mcp" + - "--enable-web" + - "--grpc-port=50051" + - "--mcp-port=50052" + - "--web-port=8081" + - "--health-port=8080" + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health/ready"] + interval: 10s + timeout: 5s + retries: 3 + start_period: 10s + deploy: + resources: + limits: + cpus: '0.5' + memory: 256M + reservations: + cpus: '0.25' + memory: 128M + # Security settings per ARTF spec + security_opt: + - no-new-privileges:true + read_only: true + cap_drop: + - ALL + # Network isolation per ARTF spec + networks: + - artf-network + + # gRPC-only service variant + artf-grpc: + build: + context: . + dockerfile: Dockerfile + profiles: ["grpc-only"] + ports: + - "50051:50051" + - "8080:8080" + command: + - "--enable-grpc" + - "--enable-mcp=false" + - "--enable-web=false" + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health/ready"] + interval: 10s + timeout: 5s + retries: 3 + security_opt: + - no-new-privileges:true + read_only: true + cap_drop: + - ALL + networks: + - artf-network + + # MCP-only service variant + artf-mcp: + build: + context: . + dockerfile: Dockerfile + profiles: ["mcp-only"] + ports: + - "50052:50052" + - "8080:8080" + command: + - "--enable-grpc=false" + - "--enable-mcp" + - "--enable-web=false" + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health/ready"] + interval: 10s + timeout: 5s + retries: 3 + security_opt: + - no-new-privileges:true + read_only: true + cap_drop: + - ALL + networks: + - artf-network + +networks: + artf-network: + driver: bridge + internal: false # Set to true for production to block external access diff --git a/docs/00-EXAMPLE.md b/docs/00-EXAMPLE.md new file mode 100644 index 0000000..ac8a9d8 --- /dev/null +++ b/docs/00-EXAMPLE.md @@ -0,0 +1,476 @@ +# Agentic RTB Framework - Implementation Specification + +This document provides a comprehensive specification for the Go reference implementation of the IAB Tech Lab's Agentic RTB Framework (ARTF) v1.0. + +--- + +## Table of Contents + +1. [Overview](#overview) +2. [Protocol Definition](#protocol-definition) +3. [Project Structure](#project-structure) +4. [API Specification](#api-specification) +5. [Message Types](#message-types) +6. [Intents and Operations](#intents-and-operations) +7. [Server Endpoints](#server-endpoints) +8. [Container Requirements](#container-requirements) +9. [Build and Deployment](#build-and-deployment) +10. [Configuration](#configuration) + +--- + +## Overview + +The Agentic RTB Framework (ARTF) defines a standard for implementing agent services that operate within a host platform's infrastructure to process bidstream data in real-time. This implementation provides: + +- **Segment Activation** - Activate user segments based on bid request data +- **Deal Management** - Activate, suppress, and adjust deals dynamically +- **Bid Shading** - Optimize bid prices using intelligent pricing strategies +- **Metrics Addition** - Add viewability and other metrics to impressions + +### Key Principles + +1. **Agents participate in the core bidstream** - Real-time transaction processing +2. **Agents accomplish specific goals** - Each agent declares specific intents +3. **Agents are composable and deployable** - OCI containers, Kubernetes-ready +4. **Agents are performant** - gRPC/protobuf, sub-millisecond latency +5. **Agents follow least-privilege** - No external network access, minimal data exposure + +--- + +## Protocol Definition + +### Service Definition + +```protobuf +syntax = "proto2"; +package com.iabtechlab.bidstream.mutation.v1; + +service RTBExtensionPoint { + // GetMutations returns RTBResponse containing mutations to be applied + // at the predetermined auction lifecycle event + rpc GetMutations (RTBRequest) returns (RTBResponse); +} +``` + +### Wire Protocol + +| Attribute | Value | +|-----------|-------| +| Protocol | gRPC over HTTP/2 | +| Serialization | Protocol Buffers (proto2) | +| Default Port | 50051 | +| TLS | Recommended for production | + +--- + +## Project Structure + +``` +agentic-rtb-framework/ +├── cmd/ +│ └── server/ +│ └── main.go # Server entry point +├── internal/ +│ ├── handlers/ +│ │ └── handlers.go # Mutation handlers +│ ├── health/ +│ │ └── health.go # Health check endpoints +│ └── server/ +│ └── server.go # gRPC service implementation +├── pkg/ +│ └── pb/ # Generated protobuf code +│ ├── artf/ # ARTF messages and service +│ └── openrtb/ # OpenRTB v2.6 messages +├── proto/ +│ ├── agenticrtbframework.proto +│ └── com/iabtechlab/openrtb/v2.6/ +│ └── openrtb.proto +├── scripts/ +│ └── generate.sh # Protobuf generation script +├── docs/ +│ └── SPECIFICATION.md # This file +├── CLAUDE.md # AI assistant instructions +├── README.md # Project readme +├── Dockerfile # Container build +├── docker-compose.yml # Local development +├── Makefile # Build automation +├── go.mod # Go module definition +└── go.sum # Dependency checksums +``` + +--- + +## API Specification + +### RTBRequest + +The request message sent from the orchestrator to the agent. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `lifecycle` | Lifecycle | Yes | Auction lifecycle stage | +| `id` | string | Yes | Unique request ID assigned by exchange | +| `tmax` | int32 | Yes | Maximum response time in milliseconds | +| `bid_request` | BidRequest | Yes | OpenRTB v2.6 bid request | +| `bid_response` | BidResponse | No | OpenRTB v2.6 bid response (if available) | +| `ext` | Extensions | No | Extension fields | + +### RTBResponse + +The response message returned by the agent. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `id` | string | Yes | Request ID (must match request) | +| `mutations` | Mutation[] | No | List of proposed mutations | +| `metadata` | Metadata | No | Response metadata | + +### Mutation + +A single atomic change proposed to the bid request or response. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `intent` | Intent | Yes | Purpose of the mutation | +| `op` | Operation | Yes | Operation type (add/remove/replace) | +| `path` | string | Yes | Semantic path to target data | +| `value` | oneof | Yes | Payload (type depends on intent) | + +### Metadata + +Optional metadata about the response. + +| Field | Type | Description | +|-------|------|-------------| +| `api_version` | string | Version of the agent API | +| `model_version` | string | Version of the ML model (if applicable) | + +--- + +## Message Types + +### Payload Types + +#### IDsPayload + +Used for segment and deal ID lists. + +```protobuf +message IDsPayload { + repeated string id = 1; +} +``` + +#### AdjustDealPayload + +Used for deal floor and margin adjustments. + +```protobuf +message AdjustDealPayload { + optional double bidfloor = 1; + optional Margin margin = 2; +} + +message Margin { + optional double value = 1; + optional CalculationType calculation_type = 2; + + enum CalculationType { + CPM = 0; // Absolute margin + PERCENT = 1; // Relative margin (percentage) + } +} +``` + +#### AdjustBidPayload + +Used for bid price adjustments. + +```protobuf +message AdjustBidPayload { + optional double price = 1; +} +``` + +#### AddMetricsPayload + +Used for adding impression metrics. + +```protobuf +message AddMetricsPayload { + repeated Metric metric = 1; // OpenRTB Metric objects +} +``` + +--- + +## Intents and Operations + +### Intent Enum + +| Value | Name | Description | +|-------|------|-------------| +| 0 | `INTENT_UNSPECIFIED` | Unspecified (invalid) | +| 1 | `ACTIVATE_SEGMENTS` | Activate user segments by external segment IDs | +| 2 | `ACTIVATE_DEALS` | Activate deals by external deal IDs | +| 3 | `SUPPRESS_DEALS` | Suppress deals by external deal IDs | +| 4 | `ADJUST_DEAL_FLOOR` | Adjust the bid floor of a specific deal | +| 5 | `ADJUST_DEAL_MARGIN` | Adjust the deal margin of a specific deal | +| 6 | `BID_SHADE` | Adjust the bid price of a specific bid | +| 7 | `ADD_METRICS` | Add metrics to an impression | + +### Operation Enum + +| Value | Name | Description | +|-------|------|-------------| +| 0 | `OPERATION_UNSPECIFIED` | Unspecified (invalid) | +| 1 | `OPERATION_ADD` | Add new data to the target | +| 2 | `OPERATION_REMOVE` | Remove data from the target | +| 3 | `OPERATION_REPLACE` | Replace existing data at the target | + +### Intent-Payload Mapping + +| Intent | Expected Payload | Path Example | +|--------|-----------------|--------------| +| `ACTIVATE_SEGMENTS` | IDsPayload | `/user/data/segment` | +| `ACTIVATE_DEALS` | IDsPayload | `/imp/{id}` | +| `SUPPRESS_DEALS` | IDsPayload | `/imp/{id}` | +| `ADJUST_DEAL_FLOOR` | AdjustDealPayload | `/imp/{id}/pmp/deals/{dealId}` | +| `ADJUST_DEAL_MARGIN` | AdjustDealPayload | `/imp/{id}/pmp/deals/{dealId}` | +| `BID_SHADE` | AdjustBidPayload | `/seatbid/{seat}/bid/{bidId}` | +| `ADD_METRICS` | AddMetricsPayload | `/imp/{id}/metric` | + +--- + +## Server Endpoints + +### gRPC Service + +| Port | Service | Method | Description | +|------|---------|--------|-------------| +| 50051 | RTBExtensionPoint | GetMutations | Process bid request and return mutations | + +### Health Check HTTP Endpoints + +| Port | Path | Method | Description | +|------|------|--------|-------------| +| 8080 | `/health/live` | GET | Liveness probe - returns 200 if process is alive | +| 8080 | `/health/ready` | GET | Readiness probe - returns 200 if ready for traffic | + +### Health Response Format + +```json +{ + "status": "ready", + "ready": true, + "version": "1.0.0" +} +``` + +--- + +## Container Requirements + +### Security Requirements (per ARTF spec) + +| Requirement | Implementation | +|-------------|----------------| +| Non-root user | `USER 65534:65534` in Dockerfile | +| Drop capabilities | `cap_drop: ALL` in docker-compose | +| Read-only filesystem | `read_only: true` in docker-compose | +| No privilege escalation | `no-new-privileges: true` | +| Network isolation | Configurable via network policies | + +### Resource Defaults + +| Resource | Default | Description | +|----------|---------|-------------| +| CPU Limit | 500m | 0.5 CPU cores | +| Memory Limit | 256Mi | 256 MB RAM | +| CPU Request | 250m | 0.25 CPU cores | +| Memory Request | 128Mi | 128 MB RAM | + +### Health Probes + +```yaml +livenessProbe: + httpGet: + path: /health/live + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 5 + +readinessProbe: + httpGet: + path: /health/ready + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 +``` + +--- + +## Build and Deployment + +### Prerequisites + +- Go 1.22+ +- Protocol Buffers compiler (`protoc`) +- protoc-gen-go and protoc-gen-go-grpc plugins +- Docker (for containerized deployment) + +### Build Commands + +| Command | Description | +|---------|-------------| +| `make deps` | Download Go dependencies | +| `make generate` | Generate protobuf Go code | +| `make build` | Build server binary | +| `make test` | Run unit tests | +| `make test-coverage` | Run tests with coverage report | +| `make lint` | Run linter | +| `make clean` | Remove build artifacts | + +### Run Commands + +| Command | Description | +|---------|-------------| +| `make run` | Run server locally | +| `make docker-build` | Build Docker image | +| `make docker-run` | Run Docker container | +| `make docker-compose-up` | Start with docker-compose | +| `make docker-compose-down` | Stop docker-compose services | + +### Testing Commands + +| Command | Description | +|---------|-------------| +| `make grpc-test` | Test gRPC endpoint with grpcurl | +| `make health-check` | Check health endpoints with curl | + +--- + +## Configuration + +### Command-Line Flags + +| Flag | Default | Description | +|------|---------|-------------| +| `-grpc-port` | 50051 | gRPC server listening port | +| `-health-port` | 8080 | Health check HTTP server port | + +### Environment Variables + +| Variable | Description | +|----------|-------------| +| `GRPC_PORT` | Override gRPC port | +| `HEALTH_PORT` | Override health check port | + +### Agent Manifest (Container Label) + +The container image must include an `agent-manifest` label with JSON metadata: + +```json +{ + "name": "artf-agent", + "version": "1.0.0", + "vendor": "your-organization", + "owner": "team@example.com", + "resources": { + "cpu": "500m", + "memory": "256Mi" + }, + "intents": [ + "ACTIVATE_SEGMENTS", + "ACTIVATE_DEALS", + "SUPPRESS_DEALS", + "ADJUST_DEAL_FLOOR", + "BID_SHADE" + ], + "dependencies": {}, + "health": { + "livenessProbe": { + "httpGet": { "path": "/health/live", "port": 8080 } + }, + "readinessProbe": { + "httpGet": { "path": "/health/ready", "port": 8080 } + } + }, + "security": { + "runAsNonRoot": true, + "dropCapabilities": ["NET_ADMIN", "SYS_PTRACE"] + } +} +``` + +--- + +## Example Mutations + +### Activate Segments + +```json +{ + "intent": "ACTIVATE_SEGMENTS", + "op": "OPERATION_ADD", + "path": "/user/data/segment", + "ids": { + "id": ["demo-18-24", "sports-enthusiast", "premium-user"] + } +} +``` + +### Activate Deals + +```json +{ + "intent": "ACTIVATE_DEALS", + "op": "OPERATION_ADD", + "path": "/imp/1", + "ids": { + "id": ["deal-001", "deal-002"] + } +} +``` + +### Adjust Deal Floor + +```json +{ + "intent": "ADJUST_DEAL_FLOOR", + "op": "OPERATION_REPLACE", + "path": "/imp/1/pmp/deals/deal-001", + "adjust_deal": { + "bidfloor": 5.50 + } +} +``` + +### Bid Shading + +```json +{ + "intent": "BID_SHADE", + "op": "OPERATION_REPLACE", + "path": "/seatbid/seat-1/bid/bid-123", + "adjust_bid": { + "price": 4.25 + } +} +``` + +--- + +## References + +- [IAB Tech Lab Agentic RTB Framework v1.0](https://iabtechlab.com/standards/artf/) +- [OpenRTB v2.6 Specification](https://iabtechlab.com/standards/openrtb/) +- [gRPC Documentation](https://grpc.io/docs/) +- [Protocol Buffers](https://protobuf.dev/) +- [OCI Container Specification](https://opencontainers.org/) + +--- + +*Document Version: 1.0.0* +*Last Updated: November 2025* diff --git a/docs/01-MCP.md b/docs/01-MCP.md new file mode 100644 index 0000000..c9815c2 --- /dev/null +++ b/docs/01-MCP.md @@ -0,0 +1,594 @@ +# MCP Server Integration + +This document describes the Model Context Protocol (MCP) server integration for the Agentic RTB Framework, enabling AI agents and LLMs to interact with RTB extension points through the standardized MCP protocol. + +--- + +## Table of Contents + +1. [Overview](#overview) +2. [Architecture](#architecture) +3. [MCP Tool Definition](#mcp-tool-definition) +4. [Transport Options](#transport-options) +5. [Web Interface](#web-interface) +6. [Sample Payloads](#sample-payloads) +7. [Configuration](#configuration) +8. [Usage Examples](#usage-examples) + +--- + +## Overview + +The ARTF implementation exposes the `RTBExtensionPoint` service via MCP as a tool called **"Extend RTB"**. This enables: + +- **AI Agent Integration** - LLMs can invoke RTB mutations through MCP tool calls +- **Streamable HTTP** - Web-based clients can interact via HTTP streaming +- **Web UI** - Built-in web component for testing and demonstration +- **Dual Protocol Support** - Run gRPC and MCP simultaneously on different ports + +### Why MCP? + +Per the ARTF specification, while gRPC is mandated for service-to-service communication, MCP enables **model-to-agent orchestration** for autonomic agentic flows. MCP provides: + +- Standardized tool definitions for LLM interaction +- JSON-RPC based communication (natural successor to REST) +- Support for both structured and streaming responses +- OAuth authentication support (MCP 2025-06-18+) + +--- + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ ARTF Agent │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ gRPC │ │ MCP │ │ Web │ │ +│ │ :50051 │ │ :50052 │ │ :8081 │ │ +│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ +│ │ │ │ │ +│ └─────────────────┼─────────────────┘ │ +│ │ │ +│ ┌──────▼───────┐ │ +│ │ Handlers │ │ +│ │ (Shared) │ │ +│ └──────────────┘ │ +│ │ +│ ┌──────────────┐ │ +│ │ Health │ │ +│ │ :8080 │ │ +│ └──────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Port Assignments + +| Port | Protocol | Service | Description | +|------|----------|---------|-------------| +| 50051 | gRPC | RTBExtensionPoint | Primary service endpoint | +| 50052 | HTTP/SSE | MCP Streamable HTTP | MCP tool endpoint | +| 8080 | HTTP | Health Checks | Liveness/readiness probes | +| 8081 | HTTP | Web UI | Testing interface | + +--- + +## MCP Tool Definition + +### Tool: "Extend RTB" + +The MCP server exposes a single tool that wraps the `RTBExtensionPoint.GetMutations` RPC. + +#### Tool Schema + +```json +{ + "name": "extend_rtb", + "description": "Process an OpenRTB bid request/response and return proposed mutations for segment activation, deal management, bid shading, and metrics", + "inputSchema": { + "type": "object", + "properties": { + "lifecycle": { + "type": "string", + "description": "Auction lifecycle stage", + "enum": ["LIFECYCLE_UNSPECIFIED"], + "default": "LIFECYCLE_UNSPECIFIED" + }, + "id": { + "type": "string", + "description": "Unique request ID" + }, + "tmax": { + "type": "integer", + "description": "Maximum response time in milliseconds", + "default": 100 + }, + "bid_request": { + "type": "object", + "description": "OpenRTB v2.6 BidRequest object", + "required": true + }, + "bid_response": { + "type": "object", + "description": "OpenRTB v2.6 BidResponse object (optional)" + } + }, + "required": ["id", "bid_request"] + } +} +``` + +#### Response Format + +```json +{ + "content": [ + { + "type": "text", + "text": "{\"id\":\"req-123\",\"mutations\":[...],\"metadata\":{...}}" + } + ] +} +``` + +--- + +## Transport Options + +### Streamable HTTP (Recommended) + +The MCP server uses Streamable HTTP transport for web compatibility: + +``` +POST /mcp - Send JSON-RPC requests +GET /mcp - Establish SSE stream for server messages +DELETE /mcp - Terminate session +``` + +#### Request Flow + +1. Client sends `initialize` request via POST +2. Server returns session ID in `Mcp-Session-Id` header +3. Client includes session ID in subsequent requests +4. Server streams responses via SSE (GET connection) + +### SSE Transport (Alternative) + +For clients requiring traditional SSE: + +``` +GET /sse - Establish SSE connection +POST /message - Send JSON-RPC messages +``` + +--- + +## Web Interface + +The built-in web interface provides: + +- **Request Builder** - Visual form for constructing ORTB payloads +- **Sample Payloads** - Pre-built examples for common scenarios +- **Live Response** - Real-time mutation results display +- **MCP Inspector** - Debug MCP message flow + +### Endpoints + +| Path | Description | +|------|-------------| +| `/` | Main web interface | +| `/api/samples` | List available sample payloads | +| `/api/samples/{name}` | Get specific sample payload | +| `/mcp` | MCP streamable HTTP endpoint | + +### Web Component + +The interface uses a custom web component `` that can be embedded: + +```html + + +``` + +--- + +## Sample Payloads + +### Basic Banner Request + +```json +{ + "id": "sample-banner-001", + "tmax": 100, + "bid_request": { + "id": "auction-123", + "imp": [ + { + "id": "imp-1", + "banner": { + "w": 300, + "h": 250, + "pos": 1 + }, + "bidfloor": 1.50, + "bidfloorcur": "USD" + } + ], + "site": { + "id": "site-456", + "domain": "example.com", + "cat": ["IAB1"], + "page": "https://example.com/article" + }, + "user": { + "id": "user-789", + "yob": 1990, + "gender": "M", + "data": [ + { + "id": "data-provider-1", + "name": "Example DMP", + "segment": [ + {"id": "seg-sports", "name": "Sports Enthusiast"} + ] + } + ] + }, + "device": { + "ua": "Mozilla/5.0...", + "ip": "192.168.1.1", + "geo": { + "country": "USA", + "region": "CA" + } + } + } +} +``` + +### Video Request with Deals + +```json +{ + "id": "sample-video-001", + "tmax": 150, + "bid_request": { + "id": "auction-456", + "imp": [ + { + "id": "imp-1", + "video": { + "mimes": ["video/mp4"], + "minduration": 15, + "maxduration": 30, + "w": 640, + "h": 480 + }, + "bidfloor": 8.00, + "pmp": { + "private_auction": 1, + "deals": [ + { + "id": "deal-premium-video", + "bidfloor": 10.00, + "at": 1 + } + ] + } + } + ], + "site": { + "id": "site-789", + "domain": "streaming.example.com", + "cat": ["IAB1-6"] + }, + "user": { + "id": "user-456", + "yob": 1985 + } + } +} +``` + +### Bid Response with Shading + +```json +{ + "id": "sample-bidshade-001", + "tmax": 100, + "bid_request": { + "id": "auction-789", + "imp": [ + { + "id": "imp-1", + "banner": {"w": 728, "h": 90}, + "bidfloor": 2.00 + } + ], + "user": {"id": "user-123"} + }, + "bid_response": { + "id": "auction-789", + "seatbid": [ + { + "seat": "dsp-001", + "bid": [ + { + "id": "bid-abc", + "impid": "imp-1", + "price": 5.50, + "adomain": ["advertiser.com"] + } + ] + } + ] + } +} +``` + +--- + +## Configuration + +### Command-Line Flags + +| Flag | Default | Description | +|------|---------|-------------| +| `--enable-grpc` | true | Enable gRPC interface | +| `--enable-mcp` | false | Enable MCP interface | +| `--enable-web` | false | Enable web interface | +| `--grpc-port` | 50051 | gRPC port | +| `--mcp-port` | 50052 | MCP port | +| `--web-port` | 8081 | Web UI port | +| `--health-port` | 8080 | Health check port | + +### Environment Variables + +| Variable | Description | +|----------|-------------| +| `ARTF_ENABLE_GRPC` | Enable gRPC server | +| `ARTF_ENABLE_MCP` | Enable MCP server | +| `ARTF_ENABLE_WEB` | Enable web interface | +| `ARTF_GRPC_PORT` | gRPC port | +| `ARTF_MCP_PORT` | MCP port | +| `ARTF_WEB_PORT` | Web UI port | + +### Example Configurations + +**gRPC Only (Production)** +```bash +./artf-agent --enable-grpc --grpc-port=50051 +``` + +**All Services (Development)** +```bash +./artf-agent --enable-grpc --enable-mcp --enable-web +``` + +**MCP Only (AI Integration)** +```bash +./artf-agent --enable-mcp --mcp-port=50052 +``` + +--- + +## Usage Examples + +### MCP Client (Python) + +```python +import httpx +import json + +MCP_ENDPOINT = "http://localhost:50052/mcp" + +# Initialize session +init_request = { + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": {"name": "test-client", "version": "1.0"} + } +} + +response = httpx.post(MCP_ENDPOINT, json=init_request) +session_id = response.headers.get("Mcp-Session-Id") + +# Call extend_rtb tool +tool_request = { + "jsonrpc": "2.0", + "id": 2, + "method": "tools/call", + "params": { + "name": "extend_rtb", + "arguments": { + "id": "test-001", + "tmax": 100, + "bid_request": { + "id": "auction-123", + "imp": [{"id": "imp-1", "banner": {"w": 300, "h": 250}}], + "user": {"id": "user-789", "yob": 1990} + } + } + } +} + +response = httpx.post( + MCP_ENDPOINT, + json=tool_request, + headers={"Mcp-Session-Id": session_id} +) +print(json.dumps(response.json(), indent=2)) +``` + +### cURL Example + +```bash +# Initialize session +curl -X POST http://localhost:50052/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": {"name": "curl", "version": "1.0"} + } + }' -i + +# Note the Mcp-Session-Id header from response, then: + +# Call tool +curl -X POST http://localhost:50052/mcp \ + -H "Content-Type: application/json" \ + -H "Mcp-Session-Id: " \ + -d '{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/call", + "params": { + "name": "extend_rtb", + "arguments": { + "id": "test-001", + "bid_request": { + "id": "auction-123", + "imp": [{"id": "imp-1", "banner": {"w": 300, "h": 250}}], + "user": {"id": "user-789", "yob": 1990} + } + } + } + }' +``` + +### Claude Desktop Configuration + +Add to `claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "artf": { + "command": "/path/to/artf-agent", + "args": ["--enable-mcp", "--mcp-port=50052"], + "env": {} + } + } +} +``` + +--- + +## Default Implementation: Segment Activation + +The default MCP handler implements segment activation based on user demographics from the RTB payload: + +### Logic + +1. **Extract User Data** - Parse `user.yob`, `user.gender`, existing segments +2. **Determine Segments** - Apply demographic rules: + - Age 18-24 → `demo-18-24` + - Age 25-34 → `demo-25-34` + - Age 35-44 → `demo-35-44` + - Age 45+ → `demo-45-plus` +3. **Enrich Segments** - Re-activate existing segments from `user.data` +4. **Return Mutations** - Generate `ACTIVATE_SEGMENTS` mutations + +### Example Response + +```json +{ + "id": "test-001", + "mutations": [ + { + "intent": "ACTIVATE_SEGMENTS", + "op": "OPERATION_ADD", + "path": "/user/data/segment", + "ids": { + "id": ["demo-25-34", "seg-sports"] + } + } + ], + "metadata": { + "api_version": "1.0", + "model_version": "v1.0.0" + } +} +``` + +--- + +## CORS Support + +The MCP interface includes full CORS (Cross-Origin Resource Sharing) support to enable web browsers to communicate with the MCP endpoint from different origins (e.g., the Web UI on port 8081 accessing MCP on port 50052). + +### CORS Headers + +| Header | Value | Description | +|--------|-------|-------------| +| `Access-Control-Allow-Origin` | `*` | Allow requests from any origin | +| `Access-Control-Allow-Methods` | `GET, POST, DELETE, OPTIONS` | Allowed HTTP methods | +| `Access-Control-Allow-Headers` | `Content-Type, Mcp-Session-Id, Last-Event-ID` | Allowed request headers | +| `Access-Control-Expose-Headers` | `Mcp-Session-Id` | Headers exposed to browser | + +### Preflight Handling + +The MCP server automatically handles `OPTIONS` preflight requests, returning a `200 OK` with appropriate CORS headers. This enables browsers to make cross-origin requests without additional configuration. + +### Example Cross-Origin Request + +```javascript +// From Web UI on localhost:8081 to MCP on localhost:50052 +fetch('http://localhost:50052/mcp', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Mcp-Session-Id': sessionId + }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 1, + method: 'tools/call', + params: { name: 'extend_rtb', arguments: {...} } + }) +}); +``` + +--- + +## Security Considerations + +### MCP-Specific Security + +- **Session Management** - Use stateful sessions in production +- **Authentication** - Implement OAuth for external access +- **Rate Limiting** - Apply per-session rate limits +- **Input Validation** - Validate all ORTB payloads +- **CORS Restrictions** - In production, restrict `Access-Control-Allow-Origin` to specific domains + +### Network Isolation + +In containerized deployments, the MCP port should be: +- Exposed only to authorized AI systems +- Protected by network policies +- Monitored for unusual patterns + +--- + +## References + +- [Model Context Protocol Specification](https://modelcontextprotocol.io/) +- [mcp-go Library](https://github.com/mark3labs/mcp-go) +- [MCP Streamable HTTP Transport](https://spec.modelcontextprotocol.io/specification/basic/transports/#streamable-http) +- [ARTF Specification](https://iabtechlab.com/standards/artf/) + +--- + +*Document Version: 0.10.0* +*Last Updated: November 2025* diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0e629ae --- /dev/null +++ b/go.mod @@ -0,0 +1,25 @@ +module github.com/iabtechlab/agentic-rtb-framework + +go 1.23.0 + +require ( + github.com/mark3labs/mcp-go v0.43.1 + google.golang.org/grpc v1.64.0 + google.golang.org/protobuf v1.34.1 +) + +require ( + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/invopop/jsonschema v0.13.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fa6ff6c --- /dev/null +++ b/go.sum @@ -0,0 +1,51 @@ +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= +github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mark3labs/mcp-go v0.43.1 h1:WXNVd+bRM/7mOzCM9zulSwn/s9YEdAxbmeh9LoRHEXY= +github.com/mark3labs/mcp-go v0.43.1/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e h1:Elxv5MwEkCI9f5SkoL6afed6NTdxaGoAo39eANBwHL8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/agent/agent.go b/internal/agent/agent.go new file mode 100644 index 0000000..47f8e45 --- /dev/null +++ b/internal/agent/agent.go @@ -0,0 +1,126 @@ +// Copyright (c) 2025 Index Exchange Inc. +// +// This file is part of the Agentic RTB Framework reference implementation. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Package agent implements the ARTF gRPC service +package agent + +import ( + "context" + "log" + "time" + + "github.com/iabtechlab/agentic-rtb-framework/internal/handlers" + pb "github.com/iabtechlab/agentic-rtb-framework/pkg/pb/artf" + "google.golang.org/grpc" +) + +// ARTFAgent implements the RTBExtensionPoint gRPC service +type ARTFAgent struct { + pb.UnimplementedRTBExtensionPointServer + handlers *handlers.MutationHandlers +} + +// NewARTFAgent creates a new ARTF agent instance +func NewARTFAgent(h *handlers.MutationHandlers) *ARTFAgent { + return &ARTFAgent{ + handlers: h, + } +} + +// GetMutations processes an RTB request and returns proposed mutations +func (a *ARTFAgent) GetMutations(ctx context.Context, req *pb.RTBRequest) (*pb.RTBResponse, error) { + startTime := time.Now() + + // Check timeout budget + tmax := req.GetTmax() + if tmax > 0 { + deadline := time.Now().Add(time.Duration(tmax) * time.Millisecond) + var cancel context.CancelFunc + ctx, cancel = context.WithDeadline(ctx, deadline) + defer cancel() + } + + // Collect mutations from all registered handlers + var mutations []*pb.Mutation + + // Process based on lifecycle stage + lifecycle := req.GetLifecycle() + bidRequest := req.GetBidRequest() + bidResponse := req.GetBidResponse() + + log.Printf("Processing request %s at lifecycle stage %v", req.GetId(), lifecycle) + + // Run segment activation handler + if segmentMutations, err := a.handlers.ProcessSegments(ctx, bidRequest); err == nil { + mutations = append(mutations, segmentMutations...) + } else { + log.Printf("Segment processing error: %v", err) + } + + // Run deal activation handler + if dealMutations, err := a.handlers.ProcessDeals(ctx, bidRequest); err == nil { + mutations = append(mutations, dealMutations...) + } else { + log.Printf("Deal processing error: %v", err) + } + + // Run bid shading handler (if bid response is present) + if bidResponse != nil { + if bidMutations, err := a.handlers.ProcessBidShading(ctx, bidRequest, bidResponse); err == nil { + mutations = append(mutations, bidMutations...) + } else { + log.Printf("Bid shading error: %v", err) + } + } + + // Build response + response := &pb.RTBResponse{ + Id: req.Id, + Mutations: mutations, + Metadata: &pb.Metadata{ + ApiVersion: stringPtr("1.0"), + ModelVersion: stringPtr("v0.10.0"), + }, + } + + log.Printf("Request %s processed in %v, returning %d mutations", + req.GetId(), time.Since(startTime), len(mutations)) + + return response, nil +} + +// LoggingInterceptor logs gRPC requests +func LoggingInterceptor( + ctx context.Context, + req interface{}, + info *grpc.UnaryServerInfo, + handler grpc.UnaryHandler, +) (interface{}, error) { + start := time.Now() + resp, err := handler(ctx, req) + log.Printf("gRPC %s took %v, error: %v", info.FullMethod, time.Since(start), err) + return resp, err +} + +// RegisterRTBExtensionPointServer registers the service with a gRPC server +func RegisterRTBExtensionPointServer(s *grpc.Server, agent *ARTFAgent) { + pb.RegisterRTBExtensionPointServer(s, agent) +} + +func stringPtr(s string) *string { + return &s +} diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go new file mode 100644 index 0000000..d405454 --- /dev/null +++ b/internal/handlers/handlers.go @@ -0,0 +1,267 @@ +// Copyright (c) 2025 Index Exchange Inc. +// +// This file is part of the Agentic RTB Framework reference implementation. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Package handlers implements mutation handlers for different ARTF intents +package handlers + +import ( + "context" + "log" + + pb "github.com/iabtechlab/agentic-rtb-framework/pkg/pb/artf" + openrtb "github.com/iabtechlab/agentic-rtb-framework/pkg/pb/openrtb" +) + +// MutationHandlers contains all registered mutation handlers +type MutationHandlers struct { + // Add any configuration or dependencies here +} + +// NewMutationHandlers creates a new handlers instance +func NewMutationHandlers() *MutationHandlers { + return &MutationHandlers{} +} + +// ProcessSegments analyzes the bid request and returns segment activation mutations +func (h *MutationHandlers) ProcessSegments(ctx context.Context, req *openrtb.BidRequest) ([]*pb.Mutation, error) { + if req == nil { + return nil, nil + } + + var mutations []*pb.Mutation + + // Example: Activate segments based on user data + // In a real implementation, this would call your ML model or segment service + user := req.GetUser() + if user != nil { + // Example segment activation based on user attributes + segments := determineUserSegments(user) + if len(segments) > 0 { + mutation := &pb.Mutation{ + Intent: pb.Intent_ACTIVATE_SEGMENTS.Enum(), + Op: pb.Operation_OPERATION_ADD.Enum(), + Path: stringPtr("/user/data/segment"), + Value: &pb.Mutation_Ids{ + Ids: &pb.IDsPayload{ + Id: segments, + }, + }, + } + mutations = append(mutations, mutation) + log.Printf("Activating %d segments for user", len(segments)) + } + } + + return mutations, nil +} + +// ProcessDeals analyzes the bid request and returns deal-related mutations +func (h *MutationHandlers) ProcessDeals(ctx context.Context, req *openrtb.BidRequest) ([]*pb.Mutation, error) { + if req == nil { + return nil, nil + } + + var mutations []*pb.Mutation + + // Process each impression + for _, imp := range req.GetImp() { + impID := imp.GetId() + + // Example: Activate deals based on impression characteristics + dealsToActivate := determineDealActivations(imp) + if len(dealsToActivate) > 0 { + mutation := &pb.Mutation{ + Intent: pb.Intent_ACTIVATE_DEALS.Enum(), + Op: pb.Operation_OPERATION_ADD.Enum(), + Path: stringPtr("/imp/" + impID), + Value: &pb.Mutation_Ids{ + Ids: &pb.IDsPayload{ + Id: dealsToActivate, + }, + }, + } + mutations = append(mutations, mutation) + log.Printf("Activating %d deals for impression %s", len(dealsToActivate), impID) + } + + // Example: Adjust deal floors + if floorAdjustment := calculateDealFloorAdjustment(imp); floorAdjustment != nil { + mutation := &pb.Mutation{ + Intent: pb.Intent_ADJUST_DEAL_FLOOR.Enum(), + Op: pb.Operation_OPERATION_REPLACE.Enum(), + Path: stringPtr("/imp/" + impID + "/pmp/deals"), + Value: &pb.Mutation_AdjustDeal{ + AdjustDeal: floorAdjustment, + }, + } + mutations = append(mutations, mutation) + } + } + + return mutations, nil +} + +// ProcessBidShading analyzes bid responses and returns bid adjustment mutations +func (h *MutationHandlers) ProcessBidShading(ctx context.Context, req *openrtb.BidRequest, resp *openrtb.BidResponse) ([]*pb.Mutation, error) { + if req == nil || resp == nil { + return nil, nil + } + + var mutations []*pb.Mutation + + // Process each seatbid + for _, seatbid := range resp.GetSeatbid() { + for _, bid := range seatbid.GetBid() { + // Calculate optimal bid price using bid shading logic + shadedPrice := calculateShadedBidPrice(req, bid) + if shadedPrice != nil && *shadedPrice != bid.GetPrice() { + mutation := &pb.Mutation{ + Intent: pb.Intent_BID_SHADE.Enum(), + Op: pb.Operation_OPERATION_REPLACE.Enum(), + Path: stringPtr("/seatbid/" + seatbid.GetSeat() + "/bid/" + bid.GetId()), + Value: &pb.Mutation_AdjustBid{ + AdjustBid: &pb.AdjustBidPayload{ + Price: shadedPrice, + }, + }, + } + mutations = append(mutations, mutation) + log.Printf("Bid shading: adjusted bid %s from %.4f to %.4f", + bid.GetId(), bid.GetPrice(), *shadedPrice) + } + } + } + + return mutations, nil +} + +// determineUserSegments analyzes user data and returns applicable segment IDs +func determineUserSegments(user *openrtb.BidRequest_User) []string { + var segments []string + + // Example logic - in production this would use ML models or segment services + // Check existing user data for segment hints + for _, data := range user.GetData() { + for _, seg := range data.GetSegment() { + if seg.GetId() != "" { + // Re-activate or enrich existing segments + segments = append(segments, seg.GetId()) + } + } + } + + // Example: Add demographic segments based on user attributes + if user.GetYob() > 0 { + age := 2024 - int(user.GetYob()) + if age >= 18 && age <= 24 { + segments = append(segments, "demo-18-24") + } else if age >= 25 && age <= 34 { + segments = append(segments, "demo-25-34") + } else if age >= 35 && age <= 44 { + segments = append(segments, "demo-35-44") + } + } + + return segments +} + +// determineDealActivations returns deal IDs to activate for an impression +func determineDealActivations(imp *openrtb.BidRequest_Imp) []string { + var deals []string + + // Example logic - check impression characteristics + bidfloor := imp.GetBidfloor() + if bidfloor >= 5.0 { + deals = append(deals, "premium-deal-001") + } + + // Check if video impression for video-specific deals + if imp.GetVideo() != nil { + deals = append(deals, "video-deal-001") + } + + // Check if native impression + if imp.GetNative() != nil { + deals = append(deals, "native-deal-001") + } + + return deals +} + +// calculateDealFloorAdjustment calculates floor adjustments for deals +func calculateDealFloorAdjustment(imp *openrtb.BidRequest_Imp) *pb.AdjustDealPayload { + pmp := imp.GetPmp() + if pmp == nil || len(pmp.GetDeals()) == 0 { + return nil + } + + // Example: Adjust floor based on time of day, inventory quality, etc. + // In production, this would use sophisticated pricing models + currentFloor := imp.GetBidfloor() + if currentFloor > 0 { + // Example: 10% floor adjustment + adjustedFloor := currentFloor * 1.1 + return &pb.AdjustDealPayload{ + Bidfloor: &adjustedFloor, + } + } + + return nil +} + +// calculateShadedBidPrice calculates the optimal shaded bid price +func calculateShadedBidPrice(req *openrtb.BidRequest, bid *openrtb.BidResponse_SeatBid_Bid) *float64 { + originalPrice := bid.GetPrice() + if originalPrice <= 0 { + return nil + } + + // Example bid shading logic + // In production, this would use ML models trained on win rate data + // to find the optimal price point + + // Simple example: shade by 5-15% based on bid floor + var shadePercent float64 + for _, imp := range req.GetImp() { + if imp.GetId() == bid.GetImpid() { + bidfloor := imp.GetBidfloor() + if bidfloor > 0 { + // More aggressive shading when far above floor + margin := originalPrice - bidfloor + if margin > bidfloor*0.5 { + shadePercent = 0.15 // 15% shade + } else if margin > bidfloor*0.2 { + shadePercent = 0.10 // 10% shade + } else { + shadePercent = 0.05 // 5% shade + } + } + break + } + } + + if shadePercent > 0 { + shadedPrice := originalPrice * (1 - shadePercent) + return &shadedPrice + } + + return nil +} + +func stringPtr(s string) *string { + return &s +} diff --git a/internal/health/health.go b/internal/health/health.go new file mode 100644 index 0000000..d810649 --- /dev/null +++ b/internal/health/health.go @@ -0,0 +1,95 @@ +// Copyright (c) 2025 Index Exchange Inc. +// +// This file is part of the Agentic RTB Framework reference implementation. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Package health implements Kubernetes-compatible health check endpoints +package health + +import ( + "encoding/json" + "net/http" + "sync" +) + +// Checker implements liveness and readiness probes +type Checker struct { + mu sync.RWMutex + ready bool +} + +// HealthResponse is the JSON response for health endpoints +type HealthResponse struct { + Status string `json:"status"` + Ready bool `json:"ready,omitempty"` + Version string `json:"version,omitempty"` +} + +// NewChecker creates a new health checker +func NewChecker() *Checker { + return &Checker{ + ready: false, + } +} + +// SetReady sets the readiness state +func (c *Checker) SetReady(ready bool) { + c.mu.Lock() + defer c.mu.Unlock() + c.ready = ready +} + +// IsReady returns the current readiness state +func (c *Checker) IsReady() bool { + c.mu.RLock() + defer c.mu.RUnlock() + return c.ready +} + +// LivenessHandler handles /health/live requests +// Returns 200 if the process is alive +func (c *Checker) LivenessHandler(w http.ResponseWriter, r *http.Request) { + response := HealthResponse{ + Status: "alive", + Version: "0.10.0", + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(response) +} + +// ReadinessHandler handles /health/ready requests +// Returns 200 if the server is ready to accept traffic, 503 otherwise +func (c *Checker) ReadinessHandler(w http.ResponseWriter, r *http.Request) { + ready := c.IsReady() + + response := HealthResponse{ + Ready: ready, + Version: "0.10.0", + } + + w.Header().Set("Content-Type", "application/json") + + if ready { + response.Status = "ready" + w.WriteHeader(http.StatusOK) + } else { + response.Status = "not_ready" + w.WriteHeader(http.StatusServiceUnavailable) + } + + json.NewEncoder(w).Encode(response) +} diff --git a/internal/mcp/mcp.go b/internal/mcp/mcp.go new file mode 100644 index 0000000..6d2ae76 --- /dev/null +++ b/internal/mcp/mcp.go @@ -0,0 +1,427 @@ +// Copyright (c) 2025 Index Exchange Inc. +// +// This file is part of the Agentic RTB Framework reference implementation. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Package mcp implements the MCP interface for ARTF +package mcp + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "time" + + "github.com/iabtechlab/agentic-rtb-framework/internal/handlers" + "github.com/mark3labs/mcp-go/mcp" + "github.com/mark3labs/mcp-go/server" +) + +// Agent wraps the MCP server with ARTF-specific functionality +type Agent struct { + mcpServer *server.MCPServer + handlers *handlers.MutationHandlers + addr string + port int +} + +// RTBRequest represents the MCP tool input for extend_rtb +type RTBRequest struct { + Lifecycle string `json:"lifecycle,omitempty"` + ID string `json:"id"` + Tmax int `json:"tmax,omitempty"` + BidRequest map[string]interface{} `json:"bid_request"` + BidResponse map[string]interface{} `json:"bid_response,omitempty"` +} + +// RTBResponse represents the MCP tool output +type RTBResponse struct { + ID string `json:"id"` + Mutations []Mutation `json:"mutations"` + Metadata Metadata `json:"metadata"` +} + +// Mutation represents a single mutation in the response +type Mutation struct { + Intent string `json:"intent"` + Op string `json:"op"` + Path string `json:"path"` + Value map[string]interface{} `json:"value,omitempty"` +} + +// Metadata contains response metadata +type Metadata struct { + APIVersion string `json:"api_version"` + ModelVersion string `json:"model_version"` +} + +// NewAgent creates a new MCP agent instance +func NewAgent(h *handlers.MutationHandlers, addr string, port int) *Agent { + // Create MCP server + mcpServer := server.NewMCPServer( + "ARTF Agent", + "0.10.0", + server.WithToolCapabilities(true), + server.WithRecovery(), + ) + + a := &Agent{ + mcpServer: mcpServer, + handlers: h, + addr: addr, + port: port, + } + + // Register the extend_rtb tool + a.registerTools() + + return a +} + +// registerTools registers the ARTF tools with the MCP server +func (a *Agent) registerTools() { + // Define the extend_rtb tool + extendRTBTool := mcp.NewTool("extend_rtb", + mcp.WithDescription("Process an OpenRTB bid request/response and return proposed mutations for segment activation, deal management, bid shading, and metrics"), + mcp.WithString("id", + mcp.Required(), + mcp.Description("Unique request ID"), + ), + mcp.WithNumber("tmax", + mcp.Description("Maximum response time in milliseconds"), + ), + mcp.WithObject("bid_request", + mcp.Required(), + mcp.Description("OpenRTB v2.6 BidRequest object"), + ), + mcp.WithObject("bid_response", + mcp.Description("OpenRTB v2.6 BidResponse object (optional)"), + ), + mcp.WithString("lifecycle", + mcp.Description("Auction lifecycle stage"), + ), + ) + + // Register tool with handler + a.mcpServer.AddTool(extendRTBTool, a.handleExtendRTB) +} + +// handleExtendRTB processes the extend_rtb tool call +func (a *Agent) handleExtendRTB(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + startTime := time.Now() + + // Extract parameters + id, err := request.RequireString("id") + if err != nil { + return mcp.NewToolResultError("missing required parameter: id"), nil + } + + // Get optional tmax + tmax := 100 // default + if tmaxVal, err := request.RequireFloat("tmax"); err == nil { + tmax = int(tmaxVal) + } + + // Get bid_request + args := request.GetArguments() + bidRequestRaw, ok := args["bid_request"].(map[string]interface{}) + if !ok { + return mcp.NewToolResultError("missing required parameter: bid_request"), nil + } + + // Get optional bid_response + var bidResponseRaw map[string]interface{} + if br, ok := args["bid_response"].(map[string]interface{}); ok { + bidResponseRaw = br + } + + log.Printf("MCP: Processing extend_rtb request %s with tmax=%d", id, tmax) + + // Process the request using handlers + response, err := a.processRequest(ctx, id, tmax, bidRequestRaw, bidResponseRaw) + if err != nil { + log.Printf("MCP: Error processing request: %v", err) + return mcp.NewToolResultError(fmt.Sprintf("processing error: %v", err)), nil + } + + // Serialize response to JSON + responseJSON, err := json.Marshal(response) + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("serialization error: %v", err)), nil + } + + log.Printf("MCP: Request %s processed in %v, returning %d mutations", + id, time.Since(startTime), len(response.Mutations)) + + return mcp.NewToolResultText(string(responseJSON)), nil +} + +// processRequest processes the RTB request and generates mutations +func (a *Agent) processRequest(ctx context.Context, id string, tmax int, bidRequest, bidResponse map[string]interface{}) (*RTBResponse, error) { + var mutations []Mutation + + // Extract user data for segment activation + if user, ok := bidRequest["user"].(map[string]interface{}); ok { + segments := a.determineUserSegments(user) + if len(segments) > 0 { + mutations = append(mutations, Mutation{ + Intent: "ACTIVATE_SEGMENTS", + Op: "OPERATION_ADD", + Path: "/user/data/segment", + Value: map[string]interface{}{ + "ids": map[string]interface{}{ + "id": segments, + }, + }, + }) + } + } + + // Process impressions for deal activation + if imps, ok := bidRequest["imp"].([]interface{}); ok { + for _, impRaw := range imps { + if imp, ok := impRaw.(map[string]interface{}); ok { + impID, _ := imp["id"].(string) + deals := a.determineDealActivations(imp) + if len(deals) > 0 { + mutations = append(mutations, Mutation{ + Intent: "ACTIVATE_DEALS", + Op: "OPERATION_ADD", + Path: "/imp/" + impID, + Value: map[string]interface{}{ + "ids": map[string]interface{}{ + "id": deals, + }, + }, + }) + } + } + } + } + + // Process bid response for bid shading + if bidResponse != nil { + if seatbids, ok := bidResponse["seatbid"].([]interface{}); ok { + for _, seatbidRaw := range seatbids { + if seatbid, ok := seatbidRaw.(map[string]interface{}); ok { + seat, _ := seatbid["seat"].(string) + if bids, ok := seatbid["bid"].([]interface{}); ok { + for _, bidRaw := range bids { + if bid, ok := bidRaw.(map[string]interface{}); ok { + bidID, _ := bid["id"].(string) + price, _ := bid["price"].(float64) + impID, _ := bid["impid"].(string) + + shadedPrice := a.calculateShadedPrice(bidRequest, impID, price) + if shadedPrice != nil && *shadedPrice != price { + mutations = append(mutations, Mutation{ + Intent: "BID_SHADE", + Op: "OPERATION_REPLACE", + Path: fmt.Sprintf("/seatbid/%s/bid/%s", seat, bidID), + Value: map[string]interface{}{ + "adjust_bid": map[string]interface{}{ + "price": *shadedPrice, + }, + }, + }) + } + } + } + } + } + } + } + } + + return &RTBResponse{ + ID: id, + Mutations: mutations, + Metadata: Metadata{ + APIVersion: "1.0", + ModelVersion: "v0.10.0", + }, + }, nil +} + +// determineUserSegments analyzes user data and returns applicable segment IDs +func (a *Agent) determineUserSegments(user map[string]interface{}) []string { + var segments []string + + // Re-activate existing segments from user.data + if data, ok := user["data"].([]interface{}); ok { + for _, dataRaw := range data { + if dataObj, ok := dataRaw.(map[string]interface{}); ok { + if segs, ok := dataObj["segment"].([]interface{}); ok { + for _, segRaw := range segs { + if seg, ok := segRaw.(map[string]interface{}); ok { + if id, ok := seg["id"].(string); ok && id != "" { + segments = append(segments, id) + } + } + } + } + } + } + } + + // Add demographic segments based on year of birth + if yob, ok := user["yob"].(float64); ok && yob > 0 { + age := 2024 - int(yob) + switch { + case age >= 18 && age <= 24: + segments = append(segments, "demo-18-24") + case age >= 25 && age <= 34: + segments = append(segments, "demo-25-34") + case age >= 35 && age <= 44: + segments = append(segments, "demo-35-44") + case age >= 45: + segments = append(segments, "demo-45-plus") + } + } + + // Add gender segment + if gender, ok := user["gender"].(string); ok { + switch gender { + case "M": + segments = append(segments, "gender-male") + case "F": + segments = append(segments, "gender-female") + } + } + + return segments +} + +// determineDealActivations returns deal IDs to activate for an impression +func (a *Agent) determineDealActivations(imp map[string]interface{}) []string { + var deals []string + + // Check bidfloor for premium deal activation + if bidfloor, ok := imp["bidfloor"].(float64); ok && bidfloor >= 5.0 { + deals = append(deals, "premium-deal-001") + } + + // Check for video impression + if _, ok := imp["video"]; ok { + deals = append(deals, "video-deal-001") + } + + // Check for native impression + if _, ok := imp["native"]; ok { + deals = append(deals, "native-deal-001") + } + + // Check for banner impression + if _, ok := imp["banner"]; ok { + deals = append(deals, "display-deal-001") + } + + return deals +} + +// calculateShadedPrice calculates the optimal shaded bid price +func (a *Agent) calculateShadedPrice(bidRequest map[string]interface{}, impID string, originalPrice float64) *float64 { + if originalPrice <= 0 { + return nil + } + + // Find the impression to get bidfloor + var bidfloor float64 + if imps, ok := bidRequest["imp"].([]interface{}); ok { + for _, impRaw := range imps { + if imp, ok := impRaw.(map[string]interface{}); ok { + if id, _ := imp["id"].(string); id == impID { + bidfloor, _ = imp["bidfloor"].(float64) + break + } + } + } + } + + if bidfloor <= 0 { + return nil + } + + // Calculate shade percentage based on margin above floor + var shadePercent float64 + margin := originalPrice - bidfloor + switch { + case margin > bidfloor*0.5: + shadePercent = 0.15 // 15% shade + case margin > bidfloor*0.2: + shadePercent = 0.10 // 10% shade + default: + shadePercent = 0.05 // 5% shade + } + + shadedPrice := originalPrice * (1 - shadePercent) + return &shadedPrice +} + +// corsMiddleware wraps an http.Handler with CORS headers +func corsMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Set CORS headers + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id, Last-Event-ID") + w.Header().Set("Access-Control-Expose-Headers", "Mcp-Session-Id") + + // Handle preflight requests + if r.Method == "OPTIONS" { + w.WriteHeader(http.StatusOK) + return + } + + next.ServeHTTP(w, r) + }) +} + +// Start starts the MCP interface using Streamable HTTP transport +func (a *Agent) Start() error { + listenAddr := fmt.Sprintf("%s:%d", a.addr, a.port) + log.Printf("MCP interface starting on %s", listenAddr) + + // Create streamable HTTP server + streamableServer := server.NewStreamableHTTPServer(a.mcpServer) + + // Create HTTP mux and wrap with CORS middleware + mux := http.NewServeMux() + mux.Handle("/mcp", corsMiddleware(streamableServer)) + + // Create and start HTTP server + httpServer := &http.Server{ + Addr: listenAddr, + Handler: mux, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + } + + return httpServer.ListenAndServe() +} + +// GetMCPServer returns the underlying MCP server for custom configuration +func (a *Agent) GetMCPServer() *server.MCPServer { + return a.mcpServer +} + +// Handler returns an HTTP handler for the MCP endpoint with CORS support. +// This can be mounted on an existing mux to serve MCP alongside other routes. +func (a *Agent) Handler() http.Handler { + streamableServer := server.NewStreamableHTTPServer(a.mcpServer) + return corsMiddleware(streamableServer) +} diff --git a/internal/web/handler.go b/internal/web/handler.go new file mode 100644 index 0000000..a621604 --- /dev/null +++ b/internal/web/handler.go @@ -0,0 +1,410 @@ +// Copyright (c) 2025 Index Exchange Inc. +// +// This file is part of the Agentic RTB Framework reference implementation. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Package web implements the web UI for ARTF testing +package web + +import ( + "embed" + "encoding/json" + "html/template" + "io/fs" + "log" + "net/http" + "path/filepath" + "strings" +) + +//go:embed static/* +var staticFiles embed.FS + +//go:embed templates/* +var templateFiles embed.FS + +// Handler provides HTTP handlers for the web interface +type Handler struct { + mcpEndpoint string + samples map[string]Sample + templates *template.Template +} + +// Sample represents a sample ORTB payload +type Sample struct { + Name string `json:"name"` + Description string `json:"description"` + Payload map[string]interface{} `json:"payload"` +} + +// NewHandler creates a new web handler +func NewHandler(mcpEndpoint string) (*Handler, error) { + // Parse templates + tmpl, err := template.ParseFS(templateFiles, "templates/*.html") + if err != nil { + return nil, err + } + + h := &Handler{ + mcpEndpoint: mcpEndpoint, + samples: make(map[string]Sample), + templates: tmpl, + } + + // Load default samples + h.loadDefaultSamples() + + return h, nil +} + +// loadDefaultSamples loads the built-in sample payloads +func (h *Handler) loadDefaultSamples() { + h.samples["banner-basic"] = Sample{ + Name: "Basic Banner Request", + Description: "A simple banner ad request with user demographics", + Payload: map[string]interface{}{ + "id": "sample-banner-001", + "tmax": 100, + "bid_request": map[string]interface{}{ + "id": "auction-123", + "imp": []interface{}{ + map[string]interface{}{ + "id": "imp-1", + "banner": map[string]interface{}{ + "w": 300, + "h": 250, + "pos": 1, + }, + "bidfloor": 1.50, + "bidfloorcur": "USD", + }, + }, + "site": map[string]interface{}{ + "id": "site-456", + "domain": "example.com", + "cat": []string{"IAB1"}, + "page": "https://example.com/article", + }, + "user": map[string]interface{}{ + "id": "user-789", + "yob": 1990, + "gender": "M", + "data": []interface{}{ + map[string]interface{}{ + "id": "data-provider-1", + "name": "Example DMP", + "segment": []interface{}{ + map[string]interface{}{ + "id": "seg-sports", + "name": "Sports Enthusiast", + }, + }, + }, + }, + }, + "device": map[string]interface{}{ + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + "ip": "192.168.1.1", + "geo": map[string]interface{}{ + "country": "USA", + "region": "CA", + }, + }, + }, + }, + } + + h.samples["video-deals"] = Sample{ + Name: "Video Request with Deals", + Description: "A video ad request with private marketplace deals", + Payload: map[string]interface{}{ + "id": "sample-video-001", + "tmax": 150, + "bid_request": map[string]interface{}{ + "id": "auction-456", + "imp": []interface{}{ + map[string]interface{}{ + "id": "imp-1", + "video": map[string]interface{}{ + "mimes": []string{"video/mp4"}, + "minduration": 15, + "maxduration": 30, + "w": 640, + "h": 480, + }, + "bidfloor": 8.00, + "pmp": map[string]interface{}{ + "private_auction": 1, + "deals": []interface{}{ + map[string]interface{}{ + "id": "deal-premium-video", + "bidfloor": 10.00, + "at": 1, + }, + }, + }, + }, + }, + "site": map[string]interface{}{ + "id": "site-789", + "domain": "streaming.example.com", + "cat": []string{"IAB1-6"}, + }, + "user": map[string]interface{}{ + "id": "user-456", + "yob": 1985, + }, + }, + }, + } + + h.samples["bid-shading"] = Sample{ + Name: "Bid Response with Shading", + Description: "A complete request/response pair for bid shading demonstration", + Payload: map[string]interface{}{ + "id": "sample-bidshade-001", + "tmax": 100, + "bid_request": map[string]interface{}{ + "id": "auction-789", + "imp": []interface{}{ + map[string]interface{}{ + "id": "imp-1", + "banner": map[string]interface{}{ + "w": 728, + "h": 90, + }, + "bidfloor": 2.00, + }, + }, + "user": map[string]interface{}{ + "id": "user-123", + "yob": 1975, + }, + }, + "bid_response": map[string]interface{}{ + "id": "auction-789", + "seatbid": []interface{}{ + map[string]interface{}{ + "seat": "dsp-001", + "bid": []interface{}{ + map[string]interface{}{ + "id": "bid-abc", + "impid": "imp-1", + "price": 5.50, + "adomain": []string{"advertiser.com"}, + }, + }, + }, + }, + }, + }, + } + + h.samples["native-ad"] = Sample{ + Name: "Native Ad Request", + Description: "A native advertising request", + Payload: map[string]interface{}{ + "id": "sample-native-001", + "tmax": 100, + "bid_request": map[string]interface{}{ + "id": "auction-native-123", + "imp": []interface{}{ + map[string]interface{}{ + "id": "imp-1", + "native": map[string]interface{}{ + "request": `{"ver":"1.2","assets":[{"id":1,"required":1,"title":{"len":90}}]}`, + "ver": "1.2", + }, + "bidfloor": 3.00, + }, + }, + "app": map[string]interface{}{ + "id": "app-123", + "name": "Example App", + "bundle": "com.example.app", + "cat": []string{"IAB9"}, + }, + "user": map[string]interface{}{ + "id": "user-mobile-456", + "yob": 2000, + }, + "device": map[string]interface{}{ + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)", + "devicetype": 4, + "os": "iOS", + "osv": "17.0", + }, + }, + }, + } + + h.samples["multi-imp"] = Sample{ + Name: "Multi-Impression Request", + Description: "A request with multiple impression opportunities", + Payload: map[string]interface{}{ + "id": "sample-multi-001", + "tmax": 120, + "bid_request": map[string]interface{}{ + "id": "auction-multi-123", + "imp": []interface{}{ + map[string]interface{}{ + "id": "imp-header", + "banner": map[string]interface{}{ + "w": 970, + "h": 250, + }, + "bidfloor": 4.00, + }, + map[string]interface{}{ + "id": "imp-sidebar", + "banner": map[string]interface{}{ + "w": 300, + "h": 600, + }, + "bidfloor": 2.50, + }, + map[string]interface{}{ + "id": "imp-footer", + "banner": map[string]interface{}{ + "w": 728, + "h": 90, + }, + "bidfloor": 1.00, + }, + }, + "site": map[string]interface{}{ + "id": "site-news", + "domain": "news.example.com", + "cat": []string{"IAB12"}, + }, + "user": map[string]interface{}{ + "id": "user-news-reader", + "yob": 1988, + "gender": "F", + }, + }, + }, + } +} + +// RegisterRoutes registers the web routes with the given mux +func (h *Handler) RegisterRoutes(mux *http.ServeMux) { + // Serve static files + staticFS, _ := fs.Sub(staticFiles, "static") + mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticFS)))) + + // API routes + mux.HandleFunc("/api/samples", h.handleListSamples) + mux.HandleFunc("/api/samples/", h.handleGetSample) + + // Specification page + mux.HandleFunc("/spec", h.handleSpec) + + // Container guide page + mux.HandleFunc("/container", h.handleContainer) + + // Main page + mux.HandleFunc("/", h.handleIndex) +} + +// handleSpec serves the ARTF specification page +func (h *Handler) handleSpec(w http.ResponseWriter, r *http.Request) { + specFile, err := staticFiles.ReadFile("static/spec.html") + if err != nil { + http.Error(w, "Specification not found", http.StatusNotFound) + return + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Write(specFile) +} + +// handleContainer serves the container guide page +func (h *Handler) handleContainer(w http.ResponseWriter, r *http.Request) { + containerFile, err := staticFiles.ReadFile("static/container.html") + if err != nil { + http.Error(w, "Container guide not found", http.StatusNotFound) + return + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Write(containerFile) +} + +// handleIndex serves the main page +func (h *Handler) handleIndex(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.NotFound(w, r) + return + } + + data := struct { + MCPEndpoint string + Samples map[string]Sample + }{ + MCPEndpoint: h.mcpEndpoint, + Samples: h.samples, + } + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + if err := h.templates.ExecuteTemplate(w, "index.html", data); err != nil { + log.Printf("Template error: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } +} + +// handleListSamples returns the list of available samples +func (h *Handler) handleListSamples(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + sampleList := make([]map[string]string, 0, len(h.samples)) + for id, sample := range h.samples { + sampleList = append(sampleList, map[string]string{ + "id": id, + "name": sample.Name, + "description": sample.Description, + }) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(sampleList) +} + +// handleGetSample returns a specific sample payload +func (h *Handler) handleGetSample(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // Extract sample ID from path + sampleID := strings.TrimPrefix(r.URL.Path, "/api/samples/") + sampleID = filepath.Clean(sampleID) + + sample, ok := h.samples[sampleID] + if !ok { + http.Error(w, "Sample not found", http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(sample.Payload) +} + +// AddSample adds a custom sample payload +func (h *Handler) AddSample(id string, sample Sample) { + h.samples[id] = sample +} diff --git a/internal/web/static/.gitkeep b/internal/web/static/.gitkeep new file mode 100644 index 0000000..49e8b43 --- /dev/null +++ b/internal/web/static/.gitkeep @@ -0,0 +1 @@ +# Static files directory diff --git a/internal/web/static/container.html b/internal/web/static/container.html new file mode 100644 index 0000000..2fac1b1 --- /dev/null +++ b/internal/web/static/container.html @@ -0,0 +1,857 @@ + + + + + + Container Guide - ARTF Agent | IAB Tech Lab + + + + + + +
+
+
+ Agentic RTB Framework v1.0 + View Specification +
+
+ +
+ +
+
+ Container Guide +

Building ARTF Agent Containers

+

Quick start guide for building, customizing, and deploying ARTF-compliant agent containers

+
+
+ +
+
+ +
+
Quick Start
+
+

Get the ARTF agent running in a container in under 2 minutes:

+ +
    +
  • +
    1
    +
    +

    Clone the repository

    +
    +
    git clone https://github.com/IABTechLab/agentic-rtb-framework.git
    +cd agentic-rtb-framework
    +
    +
    +
  • +
  • +
    2
    +
    +

    Build the Docker image

    +
    +
    make docker-build
    +
    +

    This builds the image with the proper agent-manifest label per the ARTF specification.

    +
    +
  • +
  • +
    3
    +
    +

    Run the container

    +
    +
    # Run with all interfaces enabled (gRPC, MCP, Web UI)
    +docker run -p 50051:50051 -p 8081:8081 -p 8080:8080 \
    +  artf-agent:latest --enable-grpc --enable-mcp --enable-web
    +
    +# Run with gRPC only (minimal)
    +docker run -p 50051:50051 -p 8080:8080 artf-agent:latest --enable-grpc
    +
    +
    +
  • +
  • +
    4
    +
    +

    Verify the container

    +
    +
    # Check health endpoints
    +curl http://localhost:8080/health/ready
    +
    +# Access Web UI (if enabled)
    +open http://localhost:8081
    +
    +
    +
  • +
+
+
+ + +
+
Agent Manifest
+
+

+ The ARTF specification requires containers to include an agent-manifest label in the + Docker image metadata. This manifest describes the agent's capabilities, resource requirements, and + health configuration. +

+ +

Manifest Structure

+
+
{
+  "name": "artf-reference-agent",
+  "version": "0.10.0",
+  "vendor": "IAB Tech Lab",
+  "owner": "artf@iabtechlab.com",
+  "resources": {
+    "cpu": "500m",
+    "memory": "256Mi"
+  },
+  "intents": [
+    "ACTIVATE_SEGMENTS",
+    "ACTIVATE_DEALS",
+    "SUPPRESS_DEALS",
+    "ADJUST_DEAL_FLOOR",
+    "ADJUST_DEAL_MARGIN",
+    "BID_SHADE",
+    "ADD_METRICS"
+  ],
+  "health": {
+    "livenessProbe": {
+      "httpGet": { "path": "/health/live", "port": 8080 }
+    },
+    "readinessProbe": {
+      "httpGet": { "path": "/health/ready", "port": 8080 }
+    }
+  }
+}
+
+ +
+ Viewing the Manifest: You can inspect the manifest label on any ARTF container: +
+
docker inspect artf-agent:latest --format '{{index .Config.Labels "agent-manifest"}}' | jq .
+
+
+ +

Manifest Fields

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
namestringUnique agent identifier
versionstringSemantic version of the agent
vendorstringOrganization that created the agent
ownerstringContact email for the agent owner
resourcesobjectKubernetes-style resource requests (cpu, memory)
intentsarrayList of mutation intents this agent supports
dependenciesobjectExternal services the agent requires (optional)
healthobjectKubernetes-compatible health probe configuration
+
+
+ + +
+
ARTF Container Requirements
+
+

The ARTF specification mandates several security and operational requirements for containers:

+ +
+
+

Security Requirements

+
    +
  • Non-root user: Container must run as a non-root user (e.g., nobody)
  • +
  • Read-only filesystem: Use --read-only where possible
  • +
  • No network access: Containers should only communicate with the orchestrator
  • +
  • Least privilege: Drop unnecessary Linux capabilities
  • +
+
+
+

Operational Requirements

+
    +
  • Health probes: Implement /health/live and /health/ready
  • +
  • Graceful shutdown: Handle SIGTERM for clean termination
  • +
  • Resource limits: Respect declared CPU/memory limits
  • +
  • OpenTelemetry: Support metrics and distributed tracing
  • +
+
+
+ +

Port Configuration

+ + + + + + + + + + + + + + + + + + + + + + + + + +
PortServiceDescription
50051gRPCRTBExtensionPoint gRPC service
8081Web UI / MCPWeb interface and MCP endpoint (at /mcp)
8080HealthKubernetes health probes
+
+
+ + +
+
Customizing Your Agent
+
+

+ The reference implementation provides a starting point for building your own ARTF-compliant agent. + Here's how to customize the agent for your specific use case. +

+ +

1. Fork and Clone

+
+
# Fork the repository on GitHub, then clone your fork
+git clone https://github.com/YOUR-ORG/agentic-rtb-framework.git
+cd agentic-rtb-framework
+
+ +

2. Implement Your Mutation Logic

+

+ The core mutation logic lives in internal/handlers/. Each intent has a dedicated handler + that receives the bid request/response and returns mutations. +

+
+
// internal/handlers/segments.go
+func (h *Handler) ActivateSegments(req *pb.RTBRequest) ([]*pb.Mutation, error) {
+    // Your segment activation logic here
+    // Example: Look up user in your DMP, return segment IDs
+
+    segments := h.lookupUserSegments(req.BidRequest.User.Id)
+
+    return []*pb.Mutation{{
+        Intent: pb.Intent_ACTIVATE_SEGMENTS,
+        Op:     pb.Operation_OPERATION_ADD,
+        Path:   "/user/data/segment",
+        Ids:    &pb.IDsPayload{Id: segments},
+    }}, nil
+}
+
+ +

3. Configure External Dependencies

+

+ If your agent needs to call external services (databases, APIs), declare them in the manifest + and configure the orchestrator to provide network access. +

+
+ Security Note: ARTF agents typically run in a restricted network environment. + External service access must be explicitly allowed by the host platform. +
+ +

4. Update the Manifest

+

Customize the agent-manifest in your Dockerfile or Makefile build args:

+
+
# Set your agent's manifest values
+make docker-build \
+  AGENT_NAME=my-custom-agent \
+  AGENT_VERSION=1.0.0 \
+  AGENT_VENDOR="My Company" \
+  AGENT_OWNER=team@mycompany.com
+
+ +

5. Build and Test

+
+
# Run tests
+make test
+
+# Build the container
+make docker-build
+
+# Run locally and test with the Web UI
+docker run -p 50051:50051 -p 8081:8081 -p 8080:8080 \
+  my-custom-agent:latest --enable-grpc --enable-mcp --enable-web
+
+ +

6. Deploy to Your Platform

+

Push the image to your container registry and configure the host platform to run your agent:

+
+
# Tag for your registry
+docker tag my-custom-agent:latest registry.example.com/my-custom-agent:1.0.0
+
+# Push to registry
+docker push registry.example.com/my-custom-agent:1.0.0
+
+
+
+ + +
+
Kubernetes Deployment
+
+

Example Kubernetes deployment manifest for running ARTF agents in production:

+
+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: artf-agent
+  labels:
+    app: artf-agent
+spec:
+  replicas: 3
+  selector:
+    matchLabels:
+      app: artf-agent
+  template:
+    metadata:
+      labels:
+        app: artf-agent
+    spec:
+      securityContext:
+        runAsNonRoot: true
+        runAsUser: 65534
+      containers:
+      - name: agent
+        image: artf-agent:latest
+        args:
+        - "--enable-grpc"
+        - "--enable-mcp"
+        - "--enable-web"
+        ports:
+        - name: grpc
+          containerPort: 50051
+        - name: web
+          containerPort: 8081
+        - name: health
+          containerPort: 8080
+        resources:
+          requests:
+            cpu: "500m"
+            memory: "256Mi"
+          limits:
+            cpu: "1000m"
+            memory: "512Mi"
+        livenessProbe:
+          httpGet:
+            path: /health/live
+            port: health
+          initialDelaySeconds: 5
+        readinessProbe:
+          httpGet:
+            path: /health/ready
+            port: health
+          initialDelaySeconds: 5
+        securityContext:
+          readOnlyRootFilesystem: true
+          allowPrivilegeEscalation: false
+
+
+
+
+
+ + + + diff --git a/internal/web/static/spec.html b/internal/web/static/spec.html new file mode 100644 index 0000000..1c2cd64 --- /dev/null +++ b/internal/web/static/spec.html @@ -0,0 +1,797 @@ + + + + + + ARTF Specification | IAB Tech Lab + + + + + + +
+
+
+ Agentic RTB Framework v1.0 + View Official Specification +
+
+ +
+ +
+
+ Version 1.0 +

Agentic RTB Framework

+

A specification for using agent-driven containers in OpenRTB and Digital Advertising

+
+
+ +
+
+ + +

Overview

+

+ The Agentic RTB Framework defines a foundation for implementing agent services which operate within + a host platform's infrastructure. Agents participate in OpenRTB bidstream processing by proposing + mutations to bid requests and responses through a standardized protocol. +

+

+ Key principles: +

+
    +
  • Agents must participate in the core bidstream in real-time
  • +
  • Agents must accomplish specific goals through declared intents
  • +
  • Agents must be composable and deployable as OCI containers
  • +
  • Agents must be performant (sub-millisecond latency) and efficient
  • +
  • Agents must adhere to least-privilege and least-data principles
  • +
+ +

Supported Intents

+

+ Each mutation declares an intent that describes why a change is being proposed. + This enables orchestrators to make informed decisions about accepting or rejecting mutations. +

+ +
+

ACTIVATE_SEGMENTS

+

Activate user segments by their external segment IDs for audience targeting and data enrichment.

+
+ +
+

ACTIVATE_DEALS

+

Activate private marketplace deals by their external deal IDs to enable preferred buying arrangements.

+
+ +
+

SUPPRESS_DEALS

+

Suppress specific deals from participating in the auction based on business rules or eligibility criteria.

+
+ +
+

ADJUST_DEAL_FLOOR

+

Dynamically adjust the bid floor price for a specific deal based on market conditions or optimization strategies.

+
+ +
+

ADJUST_DEAL_MARGIN

+

Modify the margin or fee structure associated with a deal, supporting both CPM and percentage-based adjustments.

+
+ +
+

BID_SHADE

+

Optimize bid prices using intelligent pricing strategies to maximize value while maintaining win rates.

+
+ +
+

ADD_METRICS

+

Add viewability, brand safety, fraud detection, or other measurement metrics to impressions.

+
+ +

Mutation Operations

+

+ Each mutation specifies an operation type that determines how the value should be applied to the target path. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
OperationDescriptionExample Use Case
OPERATION_ADDAdd new data to the target pathAdding new segments to user.data
OPERATION_REMOVERemove existing data from the target pathSuppressing a deal from the PMP
OPERATION_REPLACEReplace existing data at the target pathAdjusting a bid price or floor
+ +

Processing Flow

+

+ The ARTF framework operates at different stages of the auction lifecycle. Here is the typical flow: +

+ +
+
+
1
+
+

Bid Request Received

+

Host platform receives OpenRTB bid request from exchange or upstream partner

+
+
+
+
2
+
+

Agent Invocation (gRPC or MCP)

+

Platform invokes ARTF agent via gRPC (protobuf) or MCP (JSON-RPC) with request data

+
+
+
+
3
+
+

Analysis & Decision

+

Agent analyzes user, impression, and context data to determine appropriate mutations

+
+
+
+
4
+
+

Mutation Response

+

Agent returns list of proposed mutations with intent, operation, path, and value

+
+
+
+
5
+
+

Platform Application

+

Host platform validates and selectively applies approved mutations to the bidstream

+
+
+
+ +

Lifecycle Stages

+

+ ARTF agents can be invoked at different points in the auction lifecycle. The lifecycle + parameter indicates when the agent is being called. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StageDescriptionAvailable DataCommon Intents
PRE_AUCTIONBefore bids are solicitedBid Request onlyACTIVATE_SEGMENTS, ACTIVATE_DEALS, SUPPRESS_DEALS
POST_BIDAfter bids are receivedBid Request + Bid ResponseBID_SHADE, ADJUST_DEAL_FLOOR, ADD_METRICS
PRE_RENDERBefore ad renderingWinning bid detailsADD_METRICS
+ +

API Reference

+ +

gRPC Service Definition

+
+service RTBExtensionPoint {
+  rpc GetMutations (RTBRequest) returns (RTBResponse);
+}
+
+ +

RTBRequest Message

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeRequiredDescription
lifecycleLifecycleYesAuction lifecycle stage
idstringYesUnique request ID assigned by exchange
tmaxint32YesMaximum response time in milliseconds
bid_requestBidRequestYesOpenRTB v2.6 BidRequest object
bid_responseBidResponseNoOpenRTB v2.6 BidResponse (for POST_BID stage)
+ +

Mutation Message

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
intentIntentPurpose of the mutation (ACTIVATE_SEGMENTS, BID_SHADE, etc.)
opOperationOperation type (ADD, REMOVE, REPLACE)
pathstringSemantic path to the target (e.g., /user/data/segment)
valueoneofPayload: IDsPayload, AdjustDealPayload, AdjustBidPayload, or AddMetricsPayload
+ +

Example Response

+
+{
+  "id": "request-123",
+  "mutations": [
+    {
+      "intent": "ACTIVATE_SEGMENTS",
+      "op": "OPERATION_ADD",
+      "path": "/user/data/segment",
+      "ids": {
+        "id": ["18-35-age-segment", "soccer-watchers"]
+      }
+    }
+  ],
+  "metadata": {
+    "api_version": "1.0",
+    "model_version": "v2.1"
+  }
+}
+
+ +

Agent Manifest

+

+ Each agent container must include an agent-manifest label in its image metadata + that defines capabilities, resource requirements, and dependencies. +

+ +
+{
+  "name": "segment-activation-agent",
+  "version": "1.0.0",
+  "vendor": "example-vendor",
+  "owner": "owner@example.com",
+  "resources": {
+    "cpu": "500m",
+    "memory": "256Mi"
+  },
+  "intents": ["ACTIVATE_SEGMENTS", "ACTIVATE_DEALS"],
+  "dependencies": {
+    "audienceService": {
+      "service": "audience-svc",
+      "port": 9010
+    }
+  },
+  "health": {
+    "livenessProbe": {
+      "httpGet": { "path": "/health/live", "port": 8080 }
+    },
+    "readinessProbe": {
+      "httpGet": { "path": "/health/ready", "port": 8080 }
+    }
+  }
+}
+
+ +
+ Note: For the complete specification including all details, examples, and requirements, + please refer to the official ARTF Specification + on the IAB Tech Lab website. +
+ +
+
+ + + + diff --git a/internal/web/templates/index.html b/internal/web/templates/index.html new file mode 100644 index 0000000..d18285b --- /dev/null +++ b/internal/web/templates/index.html @@ -0,0 +1,1308 @@ + + + + + + ARTF - Agentic RTB Framework Tester | IAB Tech Lab + + + + + + + +
+
+
+ Agentic RTB Framework v1.0 + View Specification +
+
+ +
+ + +
+
+ gRPC & MCP Tester +

Extend RTB Tool

+

Test the gRPC and MCP interfaces for OpenRTB bidstream mutations

+
+
+ + +
+
+ +
+
+ MCP Endpoint: {{.MCPEndpoint}} +   |   + gRPC: localhost:50051 +
+
+ + Connecting... +
+
+ + +
+ +
+
+ +
+

Supported Intents

+

+ ARTF agents propose mutations to OpenRTB bid requests and responses through a set of defined intents. + Each intent represents a specific type of modification that can be applied during the auction lifecycle. +

+
    +
  • + ACTIVATE_SEGMENTS + Activate user segments by external segment IDs for audience targeting and data enrichment +
  • +
  • + ACTIVATE_DEALS + Activate private marketplace deals by external deal IDs to enable preferred buying +
  • +
  • + SUPPRESS_DEALS + Suppress specific deals from participating in the auction based on business rules +
  • +
  • + ADJUST_DEAL_FLOOR + Dynamically adjust the bid floor price for a specific deal +
  • +
  • + ADJUST_DEAL_MARGIN + Modify the margin or fee structure associated with a deal +
  • +
  • + BID_SHADE + Optimize bid prices using intelligent pricing strategies to maximize value +
  • +
  • + ADD_METRICS + Add viewability, brand safety, or other measurement metrics to impressions +
  • +
+
+ + +
+

Processing Flow

+

+ The ARTF framework operates at different stages of the auction lifecycle. Agents receive bid request data, + analyze it, and return proposed mutations that the host platform can apply. +

+
+
+
1
+
+
Bid Request Received
+
Host platform receives OpenRTB bid request from exchange
+
+
+
+
2
+
+
Agent Invocation (gRPC or MCP)
+
Platform invokes ARTF agent via gRPC (protobuf) or MCP (JSON-RPC) with request data
+
+
+
+
3
+
+
Analysis & Decision
+
Agent analyzes user, impression, and context data to determine mutations
+
+
+
+
4
+
+
Mutation Response
+
Agent returns list of proposed mutations with intent, operation, and path
+
+
+
+
5
+
+
Platform Application
+
Host platform validates and applies approved mutations to the bidstream
+
+
+
+
+
+ + +
+

Auction Lifecycle Stages

+

+ ARTF agents can be invoked at different points in the auction lifecycle. The lifecycle + parameter indicates when the agent is being called, allowing for stage-appropriate mutations. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StageDescriptionAvailable DataCommon Intents
PRE_AUCTIONBefore bids are solicitedBid Request onlyACTIVATE_SEGMENTS, ACTIVATE_DEALS, SUPPRESS_DEALS
POST_BIDAfter bids are receivedBid Request + Bid ResponseBID_SHADE, ADJUST_DEAL_FLOOR, ADD_METRICS
PRE_RENDERBefore ad renderingWinning bid detailsADD_METRICS
+
+ + +
+

Mutation Operations

+

+ Each mutation specifies an operation type that determines how the value should be applied to the target path. +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
OperationDescriptionExample Use Case
OPERATION_ADDAdd new data to the target pathAdding new segments to user.data
OPERATION_REMOVERemove existing data from the target pathSuppressing a deal from the PMP
OPERATION_REPLACEReplace existing data at the target pathAdjusting a bid price or floor
+
+
+
+ + +
+ +
+
Request Builder
+
+
+ +
+ {{range $id, $sample := .Samples}} + + {{end}} +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + +
+
+
+ + +
+
Response
+
+
+
+
Response will appear here after sending a request...
+
+
+
+
+
+
+ + + + + + + diff --git a/pkg/pb/artf/agenticrtbframework.pb.go b/pkg/pb/artf/agenticrtbframework.pb.go new file mode 100644 index 0000000..bf84f67 --- /dev/null +++ b/pkg/pb/artf/agenticrtbframework.pb.go @@ -0,0 +1,1155 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.5 +// protoc v3.21.12 +// source: agenticrtbframework.proto + +package artf + +import ( + openrtb "github.com/iabtechlab/agentic-rtb-framework/pkg/pb/openrtb" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Lifecycle int32 + +const ( + // Placeholder to Define Programmatic Auction Definition Stages + Lifecycle_LIFECYCLE_UNSPECIFIED Lifecycle = 0 +) + +// Enum value maps for Lifecycle. +var ( + Lifecycle_name = map[int32]string{ + 0: "LIFECYCLE_UNSPECIFIED", + } + Lifecycle_value = map[string]int32{ + "LIFECYCLE_UNSPECIFIED": 0, + } +) + +func (x Lifecycle) Enum() *Lifecycle { + p := new(Lifecycle) + *p = x + return p +} + +func (x Lifecycle) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Lifecycle) Descriptor() protoreflect.EnumDescriptor { + return file_agenticrtbframework_proto_enumTypes[0].Descriptor() +} + +func (Lifecycle) Type() protoreflect.EnumType { + return &file_agenticrtbframework_proto_enumTypes[0] +} + +func (x Lifecycle) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *Lifecycle) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = Lifecycle(num) + return nil +} + +// Deprecated: Use Lifecycle.Descriptor instead. +func (Lifecycle) EnumDescriptor() ([]byte, []int) { + return file_agenticrtbframework_proto_rawDescGZIP(), []int{0} +} + +type Operation int32 + +const ( + Operation_OPERATION_UNSPECIFIED Operation = 0 + Operation_OPERATION_ADD Operation = 1 + Operation_OPERATION_REMOVE Operation = 2 + Operation_OPERATION_REPLACE Operation = 3 +) + +// Enum value maps for Operation. +var ( + Operation_name = map[int32]string{ + 0: "OPERATION_UNSPECIFIED", + 1: "OPERATION_ADD", + 2: "OPERATION_REMOVE", + 3: "OPERATION_REPLACE", + } + Operation_value = map[string]int32{ + "OPERATION_UNSPECIFIED": 0, + "OPERATION_ADD": 1, + "OPERATION_REMOVE": 2, + "OPERATION_REPLACE": 3, + } +) + +func (x Operation) Enum() *Operation { + p := new(Operation) + *p = x + return p +} + +func (x Operation) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Operation) Descriptor() protoreflect.EnumDescriptor { + return file_agenticrtbframework_proto_enumTypes[1].Descriptor() +} + +func (Operation) Type() protoreflect.EnumType { + return &file_agenticrtbframework_proto_enumTypes[1] +} + +func (x Operation) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *Operation) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = Operation(num) + return nil +} + +// Deprecated: Use Operation.Descriptor instead. +func (Operation) EnumDescriptor() ([]byte, []int) { + return file_agenticrtbframework_proto_rawDescGZIP(), []int{1} +} + +type Intent int32 + +const ( + Intent_INTENT_UNSPECIFIED Intent = 0 + // Activate user segments by their external segment IDs + Intent_ACTIVATE_SEGMENTS Intent = 1 + // Activate deals by their external deal IDs + Intent_ACTIVATE_DEALS Intent = 2 + // Suppress deals by their external deal IDs + Intent_SUPPRESS_DEALS Intent = 3 + // Adjust the bid floor of a specific deal + Intent_ADJUST_DEAL_FLOOR Intent = 4 + // Adjust the deal margin of a specific deal + Intent_ADJUST_DEAL_MARGIN Intent = 5 + // Adjust the bid price of a specific bid + Intent_BID_SHADE Intent = 6 + // Add metrics to an impression + Intent_ADD_METRICS Intent = 7 +) + +// Enum value maps for Intent. +var ( + Intent_name = map[int32]string{ + 0: "INTENT_UNSPECIFIED", + 1: "ACTIVATE_SEGMENTS", + 2: "ACTIVATE_DEALS", + 3: "SUPPRESS_DEALS", + 4: "ADJUST_DEAL_FLOOR", + 5: "ADJUST_DEAL_MARGIN", + 6: "BID_SHADE", + 7: "ADD_METRICS", + } + Intent_value = map[string]int32{ + "INTENT_UNSPECIFIED": 0, + "ACTIVATE_SEGMENTS": 1, + "ACTIVATE_DEALS": 2, + "SUPPRESS_DEALS": 3, + "ADJUST_DEAL_FLOOR": 4, + "ADJUST_DEAL_MARGIN": 5, + "BID_SHADE": 6, + "ADD_METRICS": 7, + } +) + +func (x Intent) Enum() *Intent { + p := new(Intent) + *p = x + return p +} + +func (x Intent) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Intent) Descriptor() protoreflect.EnumDescriptor { + return file_agenticrtbframework_proto_enumTypes[2].Descriptor() +} + +func (Intent) Type() protoreflect.EnumType { + return &file_agenticrtbframework_proto_enumTypes[2] +} + +func (x Intent) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *Intent) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = Intent(num) + return nil +} + +// Deprecated: Use Intent.Descriptor instead. +func (Intent) EnumDescriptor() ([]byte, []int) { + return file_agenticrtbframework_proto_rawDescGZIP(), []int{2} +} + +// The type of margin adjustment +type Margin_CalculationType int32 + +const ( + // Absolute margin adjustment + Margin_CPM Margin_CalculationType = 0 + // Relative margin adjustment (percentage) + Margin_PERCENT Margin_CalculationType = 1 +) + +// Enum value maps for Margin_CalculationType. +var ( + Margin_CalculationType_name = map[int32]string{ + 0: "CPM", + 1: "PERCENT", + } + Margin_CalculationType_value = map[string]int32{ + "CPM": 0, + "PERCENT": 1, + } +) + +func (x Margin_CalculationType) Enum() *Margin_CalculationType { + p := new(Margin_CalculationType) + *p = x + return p +} + +func (x Margin_CalculationType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Margin_CalculationType) Descriptor() protoreflect.EnumDescriptor { + return file_agenticrtbframework_proto_enumTypes[3].Descriptor() +} + +func (Margin_CalculationType) Type() protoreflect.EnumType { + return &file_agenticrtbframework_proto_enumTypes[3] +} + +func (x Margin_CalculationType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *Margin_CalculationType) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = Margin_CalculationType(num) + return nil +} + +// Deprecated: Use Margin_CalculationType.Descriptor instead. +func (Margin_CalculationType) EnumDescriptor() ([]byte, []int) { + return file_agenticrtbframework_proto_rawDescGZIP(), []int{7, 0} +} + +type RTBRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // ENUM as per Programmatic Auction Definition IAB TL doc/spec + Lifecycle *Lifecycle `protobuf:"varint,1,req,name=lifecycle,enum=com.iabtechlab.bidstream.mutation.v1.Lifecycle" json:"lifecycle,omitempty"` + // ID of the extension point request, assigned by the exchange, and unique for the + // exchange's subsequent tracking of the responses. The exchange may use + // different values for different recipients. + // REQUIRED by the RTB specification. + Id *string `protobuf:"bytes,2,req,name=id" json:"id,omitempty"` + // Maximum time in milliseconds the exchange allows for mutations to be received including latency to avoid timeout + // REQUIRED by the RTB specification. + Tmax *int32 `protobuf:"varint,3,req,name=tmax" json:"tmax,omitempty"` + // Bid request + // REQUIRED by the RTB specification. + BidRequest *openrtb.BidRequest `protobuf:"bytes,4,req,name=bid_request,json=bidRequest" json:"bid_request,omitempty"` + // Bid response + // OPTIONAL by the RTB specification. + BidResponse *openrtb.BidResponse `protobuf:"bytes,5,opt,name=bid_response,json=bidResponse" json:"bid_response,omitempty"` + // Extension fields + Ext *Extensions `protobuf:"bytes,6,opt,name=ext" json:"ext,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RTBRequest) Reset() { + *x = RTBRequest{} + mi := &file_agenticrtbframework_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RTBRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RTBRequest) ProtoMessage() {} + +func (x *RTBRequest) ProtoReflect() protoreflect.Message { + mi := &file_agenticrtbframework_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RTBRequest.ProtoReflect.Descriptor instead. +func (*RTBRequest) Descriptor() ([]byte, []int) { + return file_agenticrtbframework_proto_rawDescGZIP(), []int{0} +} + +func (x *RTBRequest) GetLifecycle() Lifecycle { + if x != nil && x.Lifecycle != nil { + return *x.Lifecycle + } + return Lifecycle_LIFECYCLE_UNSPECIFIED +} + +func (x *RTBRequest) GetId() string { + if x != nil && x.Id != nil { + return *x.Id + } + return "" +} + +func (x *RTBRequest) GetTmax() int32 { + if x != nil && x.Tmax != nil { + return *x.Tmax + } + return 0 +} + +func (x *RTBRequest) GetBidRequest() *openrtb.BidRequest { + if x != nil { + return x.BidRequest + } + return nil +} + +func (x *RTBRequest) GetBidResponse() *openrtb.BidResponse { + if x != nil { + return x.BidResponse + } + return nil +} + +func (x *RTBRequest) GetExt() *Extensions { + if x != nil { + return x.Ext + } + return nil +} + +type Extensions struct { + state protoimpl.MessageState `protogen:"open.v1"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Extensions) Reset() { + *x = Extensions{} + mi := &file_agenticrtbframework_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Extensions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Extensions) ProtoMessage() {} + +func (x *Extensions) ProtoReflect() protoreflect.Message { + mi := &file_agenticrtbframework_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Extensions.ProtoReflect.Descriptor instead. +func (*Extensions) Descriptor() ([]byte, []int) { + return file_agenticrtbframework_proto_rawDescGZIP(), []int{1} +} + +type RTBResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // ID of the extension point request to which this is a response. + // REQUIRED by the RTB specification. + Id *string `protobuf:"bytes,1,req,name=id" json:"id,omitempty"` + // List of mutations suggesting changes to be applied + Mutations []*Mutation `protobuf:"bytes,2,rep,name=mutations" json:"mutations,omitempty"` + // Metadata about the response + Metadata *Metadata `protobuf:"bytes,3,opt,name=metadata" json:"metadata,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RTBResponse) Reset() { + *x = RTBResponse{} + mi := &file_agenticrtbframework_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RTBResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RTBResponse) ProtoMessage() {} + +func (x *RTBResponse) ProtoReflect() protoreflect.Message { + mi := &file_agenticrtbframework_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RTBResponse.ProtoReflect.Descriptor instead. +func (*RTBResponse) Descriptor() ([]byte, []int) { + return file_agenticrtbframework_proto_rawDescGZIP(), []int{2} +} + +func (x *RTBResponse) GetId() string { + if x != nil && x.Id != nil { + return *x.Id + } + return "" +} + +func (x *RTBResponse) GetMutations() []*Mutation { + if x != nil { + return x.Mutations + } + return nil +} + +func (x *RTBResponse) GetMetadata() *Metadata { + if x != nil { + return x.Metadata + } + return nil +} + +type Mutation struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The purpose of the mutation + Intent *Intent `protobuf:"varint,1,req,name=intent,enum=com.iabtechlab.bidstream.mutation.v1.Intent" json:"intent,omitempty"` + // Defines the operation to perform (e.g. add, remove, replace) on the target data at the given path + Op *Operation `protobuf:"varint,2,req,name=op,enum=com.iabtechlab.bidstream.mutation.v1.Operation" json:"op,omitempty"` + // The semantic business domain of where the operation will be applied + Path *string `protobuf:"bytes,3,req,name=path" json:"path,omitempty"` + // The structure of value depends on the specified intent. + // Reserve 100+ for intent-specific payloads + // + // Types that are valid to be assigned to Value: + // + // *Mutation_Ids + // *Mutation_AdjustDeal + // *Mutation_AdjustBid + // *Mutation_AddMetrics + Value isMutation_Value `protobuf_oneof:"value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Mutation) Reset() { + *x = Mutation{} + mi := &file_agenticrtbframework_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Mutation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Mutation) ProtoMessage() {} + +func (x *Mutation) ProtoReflect() protoreflect.Message { + mi := &file_agenticrtbframework_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Mutation.ProtoReflect.Descriptor instead. +func (*Mutation) Descriptor() ([]byte, []int) { + return file_agenticrtbframework_proto_rawDescGZIP(), []int{3} +} + +func (x *Mutation) GetIntent() Intent { + if x != nil && x.Intent != nil { + return *x.Intent + } + return Intent_INTENT_UNSPECIFIED +} + +func (x *Mutation) GetOp() Operation { + if x != nil && x.Op != nil { + return *x.Op + } + return Operation_OPERATION_UNSPECIFIED +} + +func (x *Mutation) GetPath() string { + if x != nil && x.Path != nil { + return *x.Path + } + return "" +} + +func (x *Mutation) GetValue() isMutation_Value { + if x != nil { + return x.Value + } + return nil +} + +func (x *Mutation) GetIds() *IDsPayload { + if x != nil { + if x, ok := x.Value.(*Mutation_Ids); ok { + return x.Ids + } + } + return nil +} + +func (x *Mutation) GetAdjustDeal() *AdjustDealPayload { + if x != nil { + if x, ok := x.Value.(*Mutation_AdjustDeal); ok { + return x.AdjustDeal + } + } + return nil +} + +func (x *Mutation) GetAdjustBid() *AdjustBidPayload { + if x != nil { + if x, ok := x.Value.(*Mutation_AdjustBid); ok { + return x.AdjustBid + } + } + return nil +} + +func (x *Mutation) GetAddMetrics() *AddMetricsPayload { + if x != nil { + if x, ok := x.Value.(*Mutation_AddMetrics); ok { + return x.AddMetrics + } + } + return nil +} + +type isMutation_Value interface { + isMutation_Value() +} + +type Mutation_Ids struct { + // List of string Identifiers + Ids *IDsPayload `protobuf:"bytes,100,opt,name=ids,oneof"` +} + +type Mutation_AdjustDeal struct { + // Adjust properties of a specific deal + AdjustDeal *AdjustDealPayload `protobuf:"bytes,101,opt,name=adjust_deal,json=adjustDeal,oneof"` +} + +type Mutation_AdjustBid struct { + // Adjust the bid price + AdjustBid *AdjustBidPayload `protobuf:"bytes,102,opt,name=adjust_bid,json=adjustBid,oneof"` +} + +type Mutation_AddMetrics struct { + // Add metrics or telemetry data + AddMetrics *AddMetricsPayload `protobuf:"bytes,103,opt,name=add_metrics,json=addMetrics,oneof"` +} + +func (*Mutation_Ids) isMutation_Value() {} + +func (*Mutation_AdjustDeal) isMutation_Value() {} + +func (*Mutation_AdjustBid) isMutation_Value() {} + +func (*Mutation_AddMetrics) isMutation_Value() {} + +type Metadata struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Version of the data provider API + ApiVersion *string `protobuf:"bytes,1,opt,name=api_version,json=apiVersion" json:"api_version,omitempty"` + // The model version utilized by the container or API to compute the mutation if applicable + ModelVersion *string `protobuf:"bytes,2,opt,name=model_version,json=modelVersion" json:"model_version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Metadata) Reset() { + *x = Metadata{} + mi := &file_agenticrtbframework_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Metadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Metadata) ProtoMessage() {} + +func (x *Metadata) ProtoReflect() protoreflect.Message { + mi := &file_agenticrtbframework_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Metadata.ProtoReflect.Descriptor instead. +func (*Metadata) Descriptor() ([]byte, []int) { + return file_agenticrtbframework_proto_rawDescGZIP(), []int{4} +} + +func (x *Metadata) GetApiVersion() string { + if x != nil && x.ApiVersion != nil { + return *x.ApiVersion + } + return "" +} + +func (x *Metadata) GetModelVersion() string { + if x != nil && x.ModelVersion != nil { + return *x.ModelVersion + } + return "" +} + +type IDsPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + // List of IDs. Context of the ID is determined by the intent + Id []string `protobuf:"bytes,1,rep,name=id" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *IDsPayload) Reset() { + *x = IDsPayload{} + mi := &file_agenticrtbframework_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *IDsPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IDsPayload) ProtoMessage() {} + +func (x *IDsPayload) ProtoReflect() protoreflect.Message { + mi := &file_agenticrtbframework_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IDsPayload.ProtoReflect.Descriptor instead. +func (*IDsPayload) Descriptor() ([]byte, []int) { + return file_agenticrtbframework_proto_rawDescGZIP(), []int{5} +} + +func (x *IDsPayload) GetId() []string { + if x != nil { + return x.Id + } + return nil +} + +type AdjustDealPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Suggested bid floor for the deal + Bidfloor *float64 `protobuf:"fixed64,1,opt,name=bidfloor" json:"bidfloor,omitempty"` + // Suggested margin for the deal + Margin *Margin `protobuf:"bytes,2,opt,name=margin" json:"margin,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AdjustDealPayload) Reset() { + *x = AdjustDealPayload{} + mi := &file_agenticrtbframework_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdjustDealPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdjustDealPayload) ProtoMessage() {} + +func (x *AdjustDealPayload) ProtoReflect() protoreflect.Message { + mi := &file_agenticrtbframework_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdjustDealPayload.ProtoReflect.Descriptor instead. +func (*AdjustDealPayload) Descriptor() ([]byte, []int) { + return file_agenticrtbframework_proto_rawDescGZIP(), []int{6} +} + +func (x *AdjustDealPayload) GetBidfloor() float64 { + if x != nil && x.Bidfloor != nil { + return *x.Bidfloor + } + return 0 +} + +func (x *AdjustDealPayload) GetMargin() *Margin { + if x != nil { + return x.Margin + } + return nil +} + +type Margin struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The adjusted margin value + Value *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"` + CalculationType *Margin_CalculationType `protobuf:"varint,2,opt,name=calculation_type,json=calculationType,enum=com.iabtechlab.bidstream.mutation.v1.Margin_CalculationType" json:"calculation_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Margin) Reset() { + *x = Margin{} + mi := &file_agenticrtbframework_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Margin) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Margin) ProtoMessage() {} + +func (x *Margin) ProtoReflect() protoreflect.Message { + mi := &file_agenticrtbframework_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Margin.ProtoReflect.Descriptor instead. +func (*Margin) Descriptor() ([]byte, []int) { + return file_agenticrtbframework_proto_rawDescGZIP(), []int{7} +} + +func (x *Margin) GetValue() float64 { + if x != nil && x.Value != nil { + return *x.Value + } + return 0 +} + +func (x *Margin) GetCalculationType() Margin_CalculationType { + if x != nil && x.CalculationType != nil { + return *x.CalculationType + } + return Margin_CPM +} + +type AdjustBidPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The adjusted bid price + Price *float64 `protobuf:"fixed64,1,opt,name=price" json:"price,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AdjustBidPayload) Reset() { + *x = AdjustBidPayload{} + mi := &file_agenticrtbframework_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdjustBidPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdjustBidPayload) ProtoMessage() {} + +func (x *AdjustBidPayload) ProtoReflect() protoreflect.Message { + mi := &file_agenticrtbframework_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdjustBidPayload.ProtoReflect.Descriptor instead. +func (*AdjustBidPayload) Descriptor() ([]byte, []int) { + return file_agenticrtbframework_proto_rawDescGZIP(), []int{8} +} + +func (x *AdjustBidPayload) GetPrice() float64 { + if x != nil && x.Price != nil { + return *x.Price + } + return 0 +} + +type AddMetricsPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + // List of metrics to add + Metric []*openrtb.BidRequest_Imp_Metric `protobuf:"bytes,1,rep,name=metric" json:"metric,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AddMetricsPayload) Reset() { + *x = AddMetricsPayload{} + mi := &file_agenticrtbframework_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AddMetricsPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddMetricsPayload) ProtoMessage() {} + +func (x *AddMetricsPayload) ProtoReflect() protoreflect.Message { + mi := &file_agenticrtbframework_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddMetricsPayload.ProtoReflect.Descriptor instead. +func (*AddMetricsPayload) Descriptor() ([]byte, []int) { + return file_agenticrtbframework_proto_rawDescGZIP(), []int{9} +} + +func (x *AddMetricsPayload) GetMetric() []*openrtb.BidRequest_Imp_Metric { + if x != nil { + return x.Metric + } + return nil +} + +var File_agenticrtbframework_proto protoreflect.FileDescriptor + +var file_agenticrtbframework_proto_rawDesc = string([]byte{ + 0x0a, 0x19, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x72, 0x74, 0x62, 0x66, 0x72, 0x61, 0x6d, + 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x24, 0x63, 0x6f, 0x6d, + 0x2e, 0x69, 0x61, 0x62, 0x74, 0x65, 0x63, 0x68, 0x6c, 0x61, 0x62, 0x2e, 0x62, 0x69, 0x64, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, + 0x31, 0x1a, 0x27, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x61, 0x62, 0x74, 0x65, 0x63, 0x68, 0x6c, 0x61, + 0x62, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x72, 0x74, 0x62, 0x2f, 0x76, 0x32, 0x2f, 0x6f, 0x70, 0x65, + 0x6e, 0x72, 0x74, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd6, 0x02, 0x0a, 0x0a, 0x52, + 0x54, 0x42, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4d, 0x0a, 0x09, 0x6c, 0x69, 0x66, + 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x02, 0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x63, + 0x6f, 0x6d, 0x2e, 0x69, 0x61, 0x62, 0x74, 0x65, 0x63, 0x68, 0x6c, 0x61, 0x62, 0x2e, 0x62, 0x69, + 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x09, 0x6c, + 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x02, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x6d, 0x61, 0x78, + 0x18, 0x03, 0x20, 0x02, 0x28, 0x05, 0x52, 0x04, 0x74, 0x6d, 0x61, 0x78, 0x12, 0x46, 0x0a, 0x0b, + 0x62, 0x69, 0x64, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x04, 0x20, 0x02, 0x28, + 0x0b, 0x32, 0x25, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x69, 0x61, 0x62, 0x74, 0x65, 0x63, 0x68, 0x6c, + 0x61, 0x62, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x72, 0x74, 0x62, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x69, + 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0a, 0x62, 0x69, 0x64, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x49, 0x0a, 0x0c, 0x62, 0x69, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x63, 0x6f, 0x6d, + 0x2e, 0x69, 0x61, 0x62, 0x74, 0x65, 0x63, 0x68, 0x6c, 0x61, 0x62, 0x2e, 0x6f, 0x70, 0x65, 0x6e, + 0x72, 0x74, 0x62, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x69, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x52, 0x0b, 0x62, 0x69, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x42, 0x0a, 0x03, 0x65, 0x78, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x63, + 0x6f, 0x6d, 0x2e, 0x69, 0x61, 0x62, 0x74, 0x65, 0x63, 0x68, 0x6c, 0x61, 0x62, 0x2e, 0x62, 0x69, + 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x03, + 0x65, 0x78, 0x74, 0x22, 0x16, 0x0a, 0x0a, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x2a, 0x08, 0x08, 0x64, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x22, 0xb7, 0x01, 0x0a, 0x0b, + 0x52, 0x54, 0x42, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x02, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x4c, 0x0a, 0x09, 0x6d, + 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, + 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x69, 0x61, 0x62, 0x74, 0x65, 0x63, 0x68, 0x6c, 0x61, 0x62, 0x2e, + 0x62, 0x69, 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, + 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x4a, 0x0a, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x63, 0x6f, + 0x6d, 0x2e, 0x69, 0x61, 0x62, 0x74, 0x65, 0x63, 0x68, 0x6c, 0x61, 0x62, 0x2e, 0x62, 0x69, 0x64, + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x85, 0x04, 0x0a, 0x08, 0x4d, 0x75, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x44, 0x0a, 0x06, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x02, + 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x69, 0x61, 0x62, 0x74, 0x65, 0x63, 0x68, + 0x6c, 0x61, 0x62, 0x2e, 0x62, 0x69, 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x6d, 0x75, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x52, 0x06, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x3f, 0x0a, 0x02, 0x6f, 0x70, 0x18, 0x02, + 0x20, 0x02, 0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x69, 0x61, 0x62, 0x74, 0x65, + 0x63, 0x68, 0x6c, 0x61, 0x62, 0x2e, 0x62, 0x69, 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, + 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x02, 0x6f, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, + 0x68, 0x18, 0x03, 0x20, 0x02, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x44, 0x0a, + 0x03, 0x69, 0x64, 0x73, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x63, 0x6f, 0x6d, + 0x2e, 0x69, 0x61, 0x62, 0x74, 0x65, 0x63, 0x68, 0x6c, 0x61, 0x62, 0x2e, 0x62, 0x69, 0x64, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x49, 0x44, 0x73, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x48, 0x00, 0x52, 0x03, + 0x69, 0x64, 0x73, 0x12, 0x5a, 0x0a, 0x0b, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x5f, 0x64, 0x65, + 0x61, 0x6c, 0x18, 0x65, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x69, + 0x61, 0x62, 0x74, 0x65, 0x63, 0x68, 0x6c, 0x61, 0x62, 0x2e, 0x62, 0x69, 0x64, 0x73, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x2e, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x44, 0x65, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, + 0x64, 0x48, 0x00, 0x52, 0x0a, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x44, 0x65, 0x61, 0x6c, 0x12, + 0x57, 0x0a, 0x0a, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x5f, 0x62, 0x69, 0x64, 0x18, 0x66, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x69, 0x61, 0x62, 0x74, 0x65, 0x63, + 0x68, 0x6c, 0x61, 0x62, 0x2e, 0x62, 0x69, 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x6d, + 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6a, 0x75, 0x73, + 0x74, 0x42, 0x69, 0x64, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x48, 0x00, 0x52, 0x09, 0x61, + 0x64, 0x6a, 0x75, 0x73, 0x74, 0x42, 0x69, 0x64, 0x12, 0x5a, 0x0a, 0x0b, 0x61, 0x64, 0x64, 0x5f, + 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x67, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, + 0x63, 0x6f, 0x6d, 0x2e, 0x69, 0x61, 0x62, 0x74, 0x65, 0x63, 0x68, 0x6c, 0x61, 0x62, 0x2e, 0x62, + 0x69, 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x50, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x48, 0x00, 0x52, 0x0a, 0x61, 0x64, 0x64, 0x4d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x50, 0x0a, + 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x69, + 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x6f, + 0x64, 0x65, 0x6c, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, + 0x1c, 0x0a, 0x0a, 0x49, 0x44, 0x73, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x75, 0x0a, + 0x11, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x44, 0x65, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, + 0x61, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x62, 0x69, 0x64, 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x62, 0x69, 0x64, 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x12, 0x44, + 0x0a, 0x06, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, + 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x69, 0x61, 0x62, 0x74, 0x65, 0x63, 0x68, 0x6c, 0x61, 0x62, 0x2e, + 0x62, 0x69, 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x06, 0x6d, 0x61, + 0x72, 0x67, 0x69, 0x6e, 0x22, 0xb0, 0x01, 0x0a, 0x06, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x67, 0x0a, 0x10, 0x63, 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x3c, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x69, 0x61, 0x62, 0x74, 0x65, 0x63, 0x68, 0x6c, 0x61, 0x62, + 0x2e, 0x62, 0x69, 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x6d, 0x75, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2e, 0x43, 0x61, + 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0f, 0x63, + 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0x27, + 0x0a, 0x0f, 0x43, 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x07, 0x0a, 0x03, 0x43, 0x50, 0x4d, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, + 0x52, 0x43, 0x45, 0x4e, 0x54, 0x10, 0x01, 0x22, 0x28, 0x0a, 0x10, 0x41, 0x64, 0x6a, 0x75, 0x73, + 0x74, 0x42, 0x69, 0x64, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x70, + 0x72, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, + 0x65, 0x22, 0x5d, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x50, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x48, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x69, 0x61, 0x62, + 0x74, 0x65, 0x63, 0x68, 0x6c, 0x61, 0x62, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x72, 0x74, 0x62, 0x2e, + 0x76, 0x32, 0x2e, 0x42, 0x69, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x49, 0x6d, + 0x70, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x2a, 0x26, 0x0a, 0x09, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x19, 0x0a, + 0x15, 0x4c, 0x49, 0x46, 0x45, 0x43, 0x59, 0x43, 0x4c, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, + 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x2a, 0x66, 0x0a, 0x09, 0x4f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x15, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, + 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x11, 0x0a, 0x0d, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x44, + 0x44, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, + 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x56, 0x45, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x4f, 0x50, 0x45, + 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x10, 0x03, + 0x2a, 0xae, 0x01, 0x0a, 0x06, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x12, 0x49, + 0x4e, 0x54, 0x45, 0x4e, 0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, + 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x41, 0x43, 0x54, 0x49, 0x56, 0x41, 0x54, 0x45, 0x5f, + 0x53, 0x45, 0x47, 0x4d, 0x45, 0x4e, 0x54, 0x53, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x41, 0x43, + 0x54, 0x49, 0x56, 0x41, 0x54, 0x45, 0x5f, 0x44, 0x45, 0x41, 0x4c, 0x53, 0x10, 0x02, 0x12, 0x12, + 0x0a, 0x0e, 0x53, 0x55, 0x50, 0x50, 0x52, 0x45, 0x53, 0x53, 0x5f, 0x44, 0x45, 0x41, 0x4c, 0x53, + 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x41, 0x44, 0x4a, 0x55, 0x53, 0x54, 0x5f, 0x44, 0x45, 0x41, + 0x4c, 0x5f, 0x46, 0x4c, 0x4f, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12, 0x41, 0x44, 0x4a, + 0x55, 0x53, 0x54, 0x5f, 0x44, 0x45, 0x41, 0x4c, 0x5f, 0x4d, 0x41, 0x52, 0x47, 0x49, 0x4e, 0x10, + 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x42, 0x49, 0x44, 0x5f, 0x53, 0x48, 0x41, 0x44, 0x45, 0x10, 0x06, + 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x44, 0x44, 0x5f, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x53, 0x10, + 0x07, 0x32, 0x88, 0x01, 0x0a, 0x11, 0x52, 0x54, 0x42, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, + 0x6f, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x73, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x4d, 0x75, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x30, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x69, 0x61, + 0x62, 0x74, 0x65, 0x63, 0x68, 0x6c, 0x61, 0x62, 0x2e, 0x62, 0x69, 0x64, 0x73, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x2e, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x54, 0x42, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, + 0x69, 0x61, 0x62, 0x74, 0x65, 0x63, 0x68, 0x6c, 0x61, 0x62, 0x2e, 0x62, 0x69, 0x64, 0x73, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x54, 0x42, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x39, 0x5a, 0x37, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x61, 0x62, 0x74, 0x65, + 0x63, 0x68, 0x6c, 0x61, 0x62, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x2d, 0x72, 0x74, + 0x62, 0x2d, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x70, 0x6b, 0x67, 0x2f, + 0x70, 0x62, 0x2f, 0x61, 0x72, 0x74, 0x66, +}) + +var ( + file_agenticrtbframework_proto_rawDescOnce sync.Once + file_agenticrtbframework_proto_rawDescData []byte +) + +func file_agenticrtbframework_proto_rawDescGZIP() []byte { + file_agenticrtbframework_proto_rawDescOnce.Do(func() { + file_agenticrtbframework_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_agenticrtbframework_proto_rawDesc), len(file_agenticrtbframework_proto_rawDesc))) + }) + return file_agenticrtbframework_proto_rawDescData +} + +var file_agenticrtbframework_proto_enumTypes = make([]protoimpl.EnumInfo, 4) +var file_agenticrtbframework_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_agenticrtbframework_proto_goTypes = []any{ + (Lifecycle)(0), // 0: com.iabtechlab.bidstream.mutation.v1.Lifecycle + (Operation)(0), // 1: com.iabtechlab.bidstream.mutation.v1.Operation + (Intent)(0), // 2: com.iabtechlab.bidstream.mutation.v1.Intent + (Margin_CalculationType)(0), // 3: com.iabtechlab.bidstream.mutation.v1.Margin.CalculationType + (*RTBRequest)(nil), // 4: com.iabtechlab.bidstream.mutation.v1.RTBRequest + (*Extensions)(nil), // 5: com.iabtechlab.bidstream.mutation.v1.Extensions + (*RTBResponse)(nil), // 6: com.iabtechlab.bidstream.mutation.v1.RTBResponse + (*Mutation)(nil), // 7: com.iabtechlab.bidstream.mutation.v1.Mutation + (*Metadata)(nil), // 8: com.iabtechlab.bidstream.mutation.v1.Metadata + (*IDsPayload)(nil), // 9: com.iabtechlab.bidstream.mutation.v1.IDsPayload + (*AdjustDealPayload)(nil), // 10: com.iabtechlab.bidstream.mutation.v1.AdjustDealPayload + (*Margin)(nil), // 11: com.iabtechlab.bidstream.mutation.v1.Margin + (*AdjustBidPayload)(nil), // 12: com.iabtechlab.bidstream.mutation.v1.AdjustBidPayload + (*AddMetricsPayload)(nil), // 13: com.iabtechlab.bidstream.mutation.v1.AddMetricsPayload + (*openrtb.BidRequest)(nil), // 14: com.iabtechlab.openrtb.v2.BidRequest + (*openrtb.BidResponse)(nil), // 15: com.iabtechlab.openrtb.v2.BidResponse + (*openrtb.BidRequest_Imp_Metric)(nil), // 16: com.iabtechlab.openrtb.v2.BidRequest.Imp.Metric +} +var file_agenticrtbframework_proto_depIdxs = []int32{ + 0, // 0: com.iabtechlab.bidstream.mutation.v1.RTBRequest.lifecycle:type_name -> com.iabtechlab.bidstream.mutation.v1.Lifecycle + 14, // 1: com.iabtechlab.bidstream.mutation.v1.RTBRequest.bid_request:type_name -> com.iabtechlab.openrtb.v2.BidRequest + 15, // 2: com.iabtechlab.bidstream.mutation.v1.RTBRequest.bid_response:type_name -> com.iabtechlab.openrtb.v2.BidResponse + 5, // 3: com.iabtechlab.bidstream.mutation.v1.RTBRequest.ext:type_name -> com.iabtechlab.bidstream.mutation.v1.Extensions + 7, // 4: com.iabtechlab.bidstream.mutation.v1.RTBResponse.mutations:type_name -> com.iabtechlab.bidstream.mutation.v1.Mutation + 8, // 5: com.iabtechlab.bidstream.mutation.v1.RTBResponse.metadata:type_name -> com.iabtechlab.bidstream.mutation.v1.Metadata + 2, // 6: com.iabtechlab.bidstream.mutation.v1.Mutation.intent:type_name -> com.iabtechlab.bidstream.mutation.v1.Intent + 1, // 7: com.iabtechlab.bidstream.mutation.v1.Mutation.op:type_name -> com.iabtechlab.bidstream.mutation.v1.Operation + 9, // 8: com.iabtechlab.bidstream.mutation.v1.Mutation.ids:type_name -> com.iabtechlab.bidstream.mutation.v1.IDsPayload + 10, // 9: com.iabtechlab.bidstream.mutation.v1.Mutation.adjust_deal:type_name -> com.iabtechlab.bidstream.mutation.v1.AdjustDealPayload + 12, // 10: com.iabtechlab.bidstream.mutation.v1.Mutation.adjust_bid:type_name -> com.iabtechlab.bidstream.mutation.v1.AdjustBidPayload + 13, // 11: com.iabtechlab.bidstream.mutation.v1.Mutation.add_metrics:type_name -> com.iabtechlab.bidstream.mutation.v1.AddMetricsPayload + 11, // 12: com.iabtechlab.bidstream.mutation.v1.AdjustDealPayload.margin:type_name -> com.iabtechlab.bidstream.mutation.v1.Margin + 3, // 13: com.iabtechlab.bidstream.mutation.v1.Margin.calculation_type:type_name -> com.iabtechlab.bidstream.mutation.v1.Margin.CalculationType + 16, // 14: com.iabtechlab.bidstream.mutation.v1.AddMetricsPayload.metric:type_name -> com.iabtechlab.openrtb.v2.BidRequest.Imp.Metric + 4, // 15: com.iabtechlab.bidstream.mutation.v1.RTBExtensionPoint.GetMutations:input_type -> com.iabtechlab.bidstream.mutation.v1.RTBRequest + 6, // 16: com.iabtechlab.bidstream.mutation.v1.RTBExtensionPoint.GetMutations:output_type -> com.iabtechlab.bidstream.mutation.v1.RTBResponse + 16, // [16:17] is the sub-list for method output_type + 15, // [15:16] is the sub-list for method input_type + 15, // [15:15] is the sub-list for extension type_name + 15, // [15:15] is the sub-list for extension extendee + 0, // [0:15] is the sub-list for field type_name +} + +func init() { file_agenticrtbframework_proto_init() } +func file_agenticrtbframework_proto_init() { + if File_agenticrtbframework_proto != nil { + return + } + file_agenticrtbframework_proto_msgTypes[3].OneofWrappers = []any{ + (*Mutation_Ids)(nil), + (*Mutation_AdjustDeal)(nil), + (*Mutation_AdjustBid)(nil), + (*Mutation_AddMetrics)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_agenticrtbframework_proto_rawDesc), len(file_agenticrtbframework_proto_rawDesc)), + NumEnums: 4, + NumMessages: 10, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_agenticrtbframework_proto_goTypes, + DependencyIndexes: file_agenticrtbframework_proto_depIdxs, + EnumInfos: file_agenticrtbframework_proto_enumTypes, + MessageInfos: file_agenticrtbframework_proto_msgTypes, + }.Build() + File_agenticrtbframework_proto = out.File + file_agenticrtbframework_proto_goTypes = nil + file_agenticrtbframework_proto_depIdxs = nil +} diff --git a/pkg/pb/artf/agenticrtbframework_grpc.pb.go b/pkg/pb/artf/agenticrtbframework_grpc.pb.go new file mode 100644 index 0000000..4d473bc --- /dev/null +++ b/pkg/pb/artf/agenticrtbframework_grpc.pb.go @@ -0,0 +1,123 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.0 +// - protoc v3.21.12 +// source: agenticrtbframework.proto + +package artf + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + RTBExtensionPoint_GetMutations_FullMethodName = "/com.iabtechlab.bidstream.mutation.v1.RTBExtensionPoint/GetMutations" +) + +// RTBExtensionPointClient is the client API for RTBExtensionPoint service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type RTBExtensionPointClient interface { + // GetMutations returns RTBResponse containing mutations to be applied at the predetermined auction lifecycle event + GetMutations(ctx context.Context, in *RTBRequest, opts ...grpc.CallOption) (*RTBResponse, error) +} + +type rTBExtensionPointClient struct { + cc grpc.ClientConnInterface +} + +func NewRTBExtensionPointClient(cc grpc.ClientConnInterface) RTBExtensionPointClient { + return &rTBExtensionPointClient{cc} +} + +func (c *rTBExtensionPointClient) GetMutations(ctx context.Context, in *RTBRequest, opts ...grpc.CallOption) (*RTBResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RTBResponse) + err := c.cc.Invoke(ctx, RTBExtensionPoint_GetMutations_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// RTBExtensionPointServer is the server API for RTBExtensionPoint service. +// All implementations must embed UnimplementedRTBExtensionPointServer +// for forward compatibility. +type RTBExtensionPointServer interface { + // GetMutations returns RTBResponse containing mutations to be applied at the predetermined auction lifecycle event + GetMutations(context.Context, *RTBRequest) (*RTBResponse, error) + mustEmbedUnimplementedRTBExtensionPointServer() +} + +// UnimplementedRTBExtensionPointServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedRTBExtensionPointServer struct{} + +func (UnimplementedRTBExtensionPointServer) GetMutations(context.Context, *RTBRequest) (*RTBResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetMutations not implemented") +} +func (UnimplementedRTBExtensionPointServer) mustEmbedUnimplementedRTBExtensionPointServer() {} +func (UnimplementedRTBExtensionPointServer) testEmbeddedByValue() {} + +// UnsafeRTBExtensionPointServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to RTBExtensionPointServer will +// result in compilation errors. +type UnsafeRTBExtensionPointServer interface { + mustEmbedUnimplementedRTBExtensionPointServer() +} + +func RegisterRTBExtensionPointServer(s grpc.ServiceRegistrar, srv RTBExtensionPointServer) { + // If the following call panics, it indicates UnimplementedRTBExtensionPointServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&RTBExtensionPoint_ServiceDesc, srv) +} + +func _RTBExtensionPoint_GetMutations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RTBRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RTBExtensionPointServer).GetMutations(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RTBExtensionPoint_GetMutations_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RTBExtensionPointServer).GetMutations(ctx, req.(*RTBRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// RTBExtensionPoint_ServiceDesc is the grpc.ServiceDesc for RTBExtensionPoint service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var RTBExtensionPoint_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "com.iabtechlab.bidstream.mutation.v1.RTBExtensionPoint", + HandlerType: (*RTBExtensionPointServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetMutations", + Handler: _RTBExtensionPoint_GetMutations_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "agenticrtbframework.proto", +} diff --git a/pkg/pb/openrtb/openrtb.pb.go b/pkg/pb/openrtb/openrtb.pb.go new file mode 100644 index 0000000..d3ad020 --- /dev/null +++ b/pkg/pb/openrtb/openrtb.pb.go @@ -0,0 +1,8882 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.5 +// protoc v3.21.12 +// source: com/iabtechlab/openrtb/v2/openrtb.proto + +package openrtb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// OpenRTB 2.0: types of ads that can be accepted by the exchange unless +// restricted by publisher site settings. +type BannerAdType int32 + +const ( + // "Usually mobile". + BannerAdType_XHTML_TEXT_AD BannerAdType = 1 + // "Usually mobile". + BannerAdType_XHTML_BANNER_AD BannerAdType = 2 + // Javascript must be valid XHTML (ie, script tags included). + BannerAdType_JAVASCRIPT_AD BannerAdType = 3 + // Iframe. + BannerAdType_IFRAME BannerAdType = 4 +) + +// Enum value maps for BannerAdType. +var ( + BannerAdType_name = map[int32]string{ + 1: "XHTML_TEXT_AD", + 2: "XHTML_BANNER_AD", + 3: "JAVASCRIPT_AD", + 4: "IFRAME", + } + BannerAdType_value = map[string]int32{ + "XHTML_TEXT_AD": 1, + "XHTML_BANNER_AD": 2, + "JAVASCRIPT_AD": 3, + "IFRAME": 4, + } +) + +func (x BannerAdType) Enum() *BannerAdType { + p := new(BannerAdType) + *p = x + return p +} + +func (x BannerAdType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (BannerAdType) Descriptor() protoreflect.EnumDescriptor { + return file_com_iabtechlab_openrtb_v2_openrtb_proto_enumTypes[0].Descriptor() +} + +func (BannerAdType) Type() protoreflect.EnumType { + return &file_com_iabtechlab_openrtb_v2_openrtb_proto_enumTypes[0] +} + +func (x BannerAdType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *BannerAdType) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = BannerAdType(num) + return nil +} + +// Deprecated: Use BannerAdType.Descriptor instead. +func (BannerAdType) EnumDescriptor() ([]byte, []int) { + return file_com_iabtechlab_openrtb_v2_openrtb_proto_rawDescGZIP(), []int{0} +} + +// OpenRTB Native 1.0: Core layouts. An implementing exchange may not support +// all asset variants or introduce new ones unique to that system. To be +// deprecated. +type LayoutId int32 + +const ( + LayoutId_CONTENT_WALL LayoutId = 1 + LayoutId_APP_WALL LayoutId = 2 + LayoutId_NEWS_FEED LayoutId = 3 + LayoutId_CHAT_LIST LayoutId = 4 + LayoutId_CAROUSEL LayoutId = 5 + LayoutId_CONTENT_STREAM LayoutId = 6 + LayoutId_GRID LayoutId = 7 // Exchange-specific values above 500. +) + +// Enum value maps for LayoutId. +var ( + LayoutId_name = map[int32]string{ + 1: "CONTENT_WALL", + 2: "APP_WALL", + 3: "NEWS_FEED", + 4: "CHAT_LIST", + 5: "CAROUSEL", + 6: "CONTENT_STREAM", + 7: "GRID", + } + LayoutId_value = map[string]int32{ + "CONTENT_WALL": 1, + "APP_WALL": 2, + "NEWS_FEED": 3, + "CHAT_LIST": 4, + "CAROUSEL": 5, + "CONTENT_STREAM": 6, + "GRID": 7, + } +) + +func (x LayoutId) Enum() *LayoutId { + p := new(LayoutId) + *p = x + return p +} + +func (x LayoutId) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (LayoutId) Descriptor() protoreflect.EnumDescriptor { + return file_com_iabtechlab_openrtb_v2_openrtb_proto_enumTypes[1].Descriptor() +} + +func (LayoutId) Type() protoreflect.EnumType { + return &file_com_iabtechlab_openrtb_v2_openrtb_proto_enumTypes[1] +} + +func (x LayoutId) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *LayoutId) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = LayoutId(num) + return nil +} + +// Deprecated: Use LayoutId.Descriptor instead. +func (LayoutId) EnumDescriptor() ([]byte, []int) { + return file_com_iabtechlab_openrtb_v2_openrtb_proto_rawDescGZIP(), []int{1} +} + +// OpenRTB Native 1.0: Below is a list of the core ad unit ids described by IAB: +// http://www.iab.net/media/file/IABNativeAdvertisingPlaybook120413.pdf In feed +// unit is essentially a layout, it has been removed from the list. In feed +// units can be identified via the layout parameter on the request. An +// implementing exchange may not support all asset variants or introduce new +// ones unique to that system. To be deprecated. +type AdUnitId int32 + +const ( + AdUnitId_PAID_SEARCH_UNIT AdUnitId = 1 + AdUnitId_RECOMMENDATION_WIDGET AdUnitId = 2 + AdUnitId_PROMOTED_LISTING AdUnitId = 3 + AdUnitId_IAB_IN_AD_NATIVE AdUnitId = 4 + AdUnitId_ADUNITID_CUSTOM AdUnitId = 5 // Exchange-specific values above 500. +) + +// Enum value maps for AdUnitId. +var ( + AdUnitId_name = map[int32]string{ + 1: "PAID_SEARCH_UNIT", + 2: "RECOMMENDATION_WIDGET", + 3: "PROMOTED_LISTING", + 4: "IAB_IN_AD_NATIVE", + 5: "ADUNITID_CUSTOM", + } + AdUnitId_value = map[string]int32{ + "PAID_SEARCH_UNIT": 1, + "RECOMMENDATION_WIDGET": 2, + "PROMOTED_LISTING": 3, + "IAB_IN_AD_NATIVE": 4, + "ADUNITID_CUSTOM": 5, + } +) + +func (x AdUnitId) Enum() *AdUnitId { + p := new(AdUnitId) + *p = x + return p +} + +func (x AdUnitId) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (AdUnitId) Descriptor() protoreflect.EnumDescriptor { + return file_com_iabtechlab_openrtb_v2_openrtb_proto_enumTypes[2].Descriptor() +} + +func (AdUnitId) Type() protoreflect.EnumType { + return &file_com_iabtechlab_openrtb_v2_openrtb_proto_enumTypes[2] +} + +func (x AdUnitId) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *AdUnitId) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = AdUnitId(num) + return nil +} + +// Deprecated: Use AdUnitId.Descriptor instead. +func (AdUnitId) EnumDescriptor() ([]byte, []int) { + return file_com_iabtechlab_openrtb_v2_openrtb_proto_rawDescGZIP(), []int{2} +} + +// OpenRTB Native 1.1: The context in which the ad appears - what type of +// content is surrounding the ad on the page at a high level. This maps directly +// to the new Deep Dive on In-Feed Ad Units. This denotes the primary context, +// but does not imply other content may not exist on the page - for example it's +// expected that most content platforms have some social components, etc. +type ContextType int32 + +const ( + // Content-centric context such as newsfeed, article, image gallery, video + // gallery, or similar. + ContextType_CONTENT ContextType = 1 + // Social-centric context such as social network feed, email, chat, or + // similar. + ContextType_SOCIAL ContextType = 2 + // Product context such as product listings, details, recommendations, + // reviews, or similar. + ContextType_PRODUCT ContextType = 3 +) + +// Enum value maps for ContextType. +var ( + ContextType_name = map[int32]string{ + 1: "CONTENT", + 2: "SOCIAL", + 3: "PRODUCT", + } + ContextType_value = map[string]int32{ + "CONTENT": 1, + "SOCIAL": 2, + "PRODUCT": 3, + } +) + +func (x ContextType) Enum() *ContextType { + p := new(ContextType) + *p = x + return p +} + +func (x ContextType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ContextType) Descriptor() protoreflect.EnumDescriptor { + return file_com_iabtechlab_openrtb_v2_openrtb_proto_enumTypes[3].Descriptor() +} + +func (ContextType) Type() protoreflect.EnumType { + return &file_com_iabtechlab_openrtb_v2_openrtb_proto_enumTypes[3] +} + +func (x ContextType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *ContextType) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = ContextType(num) + return nil +} + +// Deprecated: Use ContextType.Descriptor instead. +func (ContextType) EnumDescriptor() ([]byte, []int) { + return file_com_iabtechlab_openrtb_v2_openrtb_proto_rawDescGZIP(), []int{3} +} + +// OpenRTB 2.0: The top-level bid request object contains a globally unique bid +// request or auction ID. This id attribute is required as is at least one +// impression object (Section 3.2.2). Other attributes in this top-level object +// establish rules and restrictions that apply to all impressions being offered. +// +// There are also several subordinate objects that provide detailed data to +// potential buyers. Among these are the Site and App objects, which describe +// the type of published media in which the impression(s) appear. These objects +// are highly recommended, but only one applies to a given bid request depending +// on whether the media is browser-based web content or a non-browser +// application, respectively. +type BidRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // ID of the bid request, assigned by the exchange, and unique for the + // exchange's subsequent tracking of the responses. The exchange may use + // different values for different recipients. + // REQUIRED by the OpenRTB specification. + Id *string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` + // Array of Imp objects (Section 3.2.4) representing the impressions offered. + // At least 1 Imp object is required. + Imp []*BidRequest_Imp `protobuf:"bytes,2,rep,name=imp" json:"imp,omitempty"` + // Types that are valid to be assigned to DistributionchannelOneof: + // + // *BidRequest_Site_ + // *BidRequest_App_ + // *BidRequest_Dooh_ + DistributionchannelOneof isBidRequest_DistributionchannelOneof `protobuf_oneof:"distributionchannel_oneof"` + // Details via a Device object (Section 3.2.18) about the user's device to + // which the impression will be delivered. + Device *BidRequest_Device `protobuf:"bytes,5,opt,name=device" json:"device,omitempty"` + // Details via a User object (Section 3.2.20) about the human user of the + // device; the advertising audience. + User *BidRequest_User `protobuf:"bytes,6,opt,name=user" json:"user,omitempty"` + // Indicator of test mode in which auctions are not billable, where 0 = live + // mode, 1 = test mode. + Test *bool `protobuf:"varint,15,opt,name=test,def=0" json:"test,omitempty"` + // Auction type, where 1 = First Price, 2 = Second Price Plus. + // Exchange-specific auction types can be defined using values 500 and + // greater. + // Refer to enum com.iabtechlab.openrtb.v3.AuctionType for values. + At *int32 `protobuf:"varint,7,opt,name=at,def=2" json:"at,omitempty"` + // Maximum time in milliseconds the exchange allows for bids to be received + // including Internet latency to avoid timeout. This value supersedes any a + // priori guidance from the exchange. + Tmax *int32 `protobuf:"varint,8,opt,name=tmax" json:"tmax,omitempty"` + // Allowed list of buyer seats (e.g., advertisers, agencies) allowed to bid on + // this impression. IDs of seats and knowledge of the buyer's customers to + // which they refer must be coordinated between bidders and the exchange a + // priori. At most, only one of wseat and bseat should be used in the same + // request. Omission of both implies no seat restrictions. + Wseat []string `protobuf:"bytes,9,rep,name=wseat" json:"wseat,omitempty"` + // Block list of buyer seats (e.g., advertisers, agencies) restricted from + // bidding on this impression. IDs of seats and knowledge of the buyer's + // customers to which they refer must be coordinated between bidders and the + // exchange a priori. At most, only one of wseat and bseat should be used in + // the same request. Omission of both implies no seat restrictions. + Bseat []string `protobuf:"bytes,17,rep,name=bseat" json:"bseat,omitempty"` + // Flag to indicate if Exchange can verify that the impressions offered + // represent all of the impressions available in context (e.g., all on the web + // page, all video spots such as pre/mid/post roll) to support road-blocking. + // 0 = no or unknown, 1 = yes, the impressions offered represent all that are + // available. + Allimps *bool `protobuf:"varint,10,opt,name=allimps,def=0" json:"allimps,omitempty"` + // Array of allowed currencies for bids on this bid request using ISO-4217 + // alpha codes. Recommended only if the exchange accepts multiple currencies. + Cur []string `protobuf:"bytes,11,rep,name=cur" json:"cur,omitempty"` + // Allowed list of languages for creatives using ISO-639-1-alpha-2. Omission + // implies no specific restrictions, but buyers would be advised to consider + // language attribute in the Device and/or Content objects if available. Only + // one of wlang or wlangb should be present. + Wlang []string `protobuf:"bytes,18,rep,name=wlang" json:"wlang,omitempty"` + // Allowed list of languages for creatives using IETF BCP 47I. Omission + // implies no specific restrictions, but buyers would be advised to consider + // language attribute in the Device and/or Content objects if available. Only + // one of wlang or wlangb should be present. + Wlangb []string `protobuf:"bytes,20,rep,name=wlangb" json:"wlangb,omitempty"` + // Allowed advertiser categories using the specified category taxonomy. The + // taxonomy to be used is defined by the cattax field. If no cattax field is + // supplied IAB Content Taxonomy 1.0 is assumed. Only one of acat or bcat + // should be present. + Acat []string `protobuf:"bytes,23,rep,name=acat" json:"acat,omitempty"` + // Blocked advertiser categories using the IAB content categories. The + // taxonomy to be used is defined by the cattax field. If no cattax field is + // supplied IAB Content Taxonomy 1.0 is assumed. Only one of acat or bcat + // should be present. + Bcat []string `protobuf:"bytes,12,rep,name=bcat" json:"bcat,omitempty"` + // The taxonomy in use for bcat. + // Refer to enum com.iabtechlab.adcom.v1.enums.CategoryTaxonomy for values. + Cattax *int32 `protobuf:"varint,21,opt,name=cattax,def=1" json:"cattax,omitempty"` + // Block list of advertisers by their domains (e.g., "ford.com"). + Badv []string `protobuf:"bytes,13,rep,name=badv" json:"badv,omitempty"` + // Block list of applications by their app store IDs. See OTT/CTV Store + // Assigned App Identification Guidelines for more details about expected + // strings for CTV app stores. For mobile apps in Google Play Store, these + // should be bundle or package names (e.g., com.foo.mygame). For apps in Apple + // App Store, these should be a numeric ID. + Bapp []string `protobuf:"bytes,16,rep,name=bapp" json:"bapp,omitempty"` + // A Source object (Section 3.2.2) that provides data about the inventory + // source and which entity makes the final decision. + Source *BidRequest_Source `protobuf:"bytes,19,opt,name=source" json:"source,omitempty"` + // A Regs object (Section 3.2.16) that specifies any industry, legal, or + // governmental regulations in force for this request. + Regs *BidRequest_Regs `protobuf:"bytes,14,opt,name=regs" json:"regs,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +// Default values for BidRequest fields. +const ( + Default_BidRequest_Test = bool(false) + Default_BidRequest_At = int32(2) + Default_BidRequest_Allimps = bool(false) + Default_BidRequest_Cattax = int32(1) +) + +func (x *BidRequest) Reset() { + *x = BidRequest{} + mi := &file_com_iabtechlab_openrtb_v2_openrtb_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BidRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BidRequest) ProtoMessage() {} + +func (x *BidRequest) ProtoReflect() protoreflect.Message { + mi := &file_com_iabtechlab_openrtb_v2_openrtb_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BidRequest.ProtoReflect.Descriptor instead. +func (*BidRequest) Descriptor() ([]byte, []int) { + return file_com_iabtechlab_openrtb_v2_openrtb_proto_rawDescGZIP(), []int{0} +} + +func (x *BidRequest) GetId() string { + if x != nil && x.Id != nil { + return *x.Id + } + return "" +} + +func (x *BidRequest) GetImp() []*BidRequest_Imp { + if x != nil { + return x.Imp + } + return nil +} + +func (x *BidRequest) GetDistributionchannelOneof() isBidRequest_DistributionchannelOneof { + if x != nil { + return x.DistributionchannelOneof + } + return nil +} + +func (x *BidRequest) GetSite() *BidRequest_Site { + if x != nil { + if x, ok := x.DistributionchannelOneof.(*BidRequest_Site_); ok { + return x.Site + } + } + return nil +} + +func (x *BidRequest) GetApp() *BidRequest_App { + if x != nil { + if x, ok := x.DistributionchannelOneof.(*BidRequest_App_); ok { + return x.App + } + } + return nil +} + +func (x *BidRequest) GetDooh() *BidRequest_Dooh { + if x != nil { + if x, ok := x.DistributionchannelOneof.(*BidRequest_Dooh_); ok { + return x.Dooh + } + } + return nil +} + +func (x *BidRequest) GetDevice() *BidRequest_Device { + if x != nil { + return x.Device + } + return nil +} + +func (x *BidRequest) GetUser() *BidRequest_User { + if x != nil { + return x.User + } + return nil +} + +func (x *BidRequest) GetTest() bool { + if x != nil && x.Test != nil { + return *x.Test + } + return Default_BidRequest_Test +} + +func (x *BidRequest) GetAt() int32 { + if x != nil && x.At != nil { + return *x.At + } + return Default_BidRequest_At +} + +func (x *BidRequest) GetTmax() int32 { + if x != nil && x.Tmax != nil { + return *x.Tmax + } + return 0 +} + +func (x *BidRequest) GetWseat() []string { + if x != nil { + return x.Wseat + } + return nil +} + +func (x *BidRequest) GetBseat() []string { + if x != nil { + return x.Bseat + } + return nil +} + +func (x *BidRequest) GetAllimps() bool { + if x != nil && x.Allimps != nil { + return *x.Allimps + } + return Default_BidRequest_Allimps +} + +func (x *BidRequest) GetCur() []string { + if x != nil { + return x.Cur + } + return nil +} + +func (x *BidRequest) GetWlang() []string { + if x != nil { + return x.Wlang + } + return nil +} + +func (x *BidRequest) GetWlangb() []string { + if x != nil { + return x.Wlangb + } + return nil +} + +func (x *BidRequest) GetAcat() []string { + if x != nil { + return x.Acat + } + return nil +} + +func (x *BidRequest) GetBcat() []string { + if x != nil { + return x.Bcat + } + return nil +} + +func (x *BidRequest) GetCattax() int32 { + if x != nil && x.Cattax != nil { + return *x.Cattax + } + return Default_BidRequest_Cattax +} + +func (x *BidRequest) GetBadv() []string { + if x != nil { + return x.Badv + } + return nil +} + +func (x *BidRequest) GetBapp() []string { + if x != nil { + return x.Bapp + } + return nil +} + +func (x *BidRequest) GetSource() *BidRequest_Source { + if x != nil { + return x.Source + } + return nil +} + +func (x *BidRequest) GetRegs() *BidRequest_Regs { + if x != nil { + return x.Regs + } + return nil +} + +type isBidRequest_DistributionchannelOneof interface { + isBidRequest_DistributionchannelOneof() +} + +type BidRequest_Site_ struct { + // Details via a Site object (Section 3.2.13) about the publisher's website. + // Only applicable and recommended for websites. + Site *BidRequest_Site `protobuf:"bytes,3,opt,name=site,oneof"` +} + +type BidRequest_App_ struct { + // Details via an App object (Section 3.2.14) about the publisher's app + // (non-browser applications). Only applicable and recommended for apps. + App *BidRequest_App `protobuf:"bytes,4,opt,name=app,oneof"` +} + +type BidRequest_Dooh_ struct { + // This object should be included if the ad supported content is a Digital + // Out-Of-Home screen. A bid request with a DOOH object must not contain a + // site or app object. + Dooh *BidRequest_Dooh `protobuf:"bytes,22,opt,name=dooh,oneof"` +} + +func (*BidRequest_Site_) isBidRequest_DistributionchannelOneof() {} + +func (*BidRequest_App_) isBidRequest_DistributionchannelOneof() {} + +func (*BidRequest_Dooh_) isBidRequest_DistributionchannelOneof() {} + +// This object is the top-level bid response object (i.e., the unnamed outer +// JSON object). The id attribute reflects the bid request ID for logging +// purposes. Similarly, bidid is an optional response tracking ID for bidders. +// If specified, it can be included in the subsequent win notice call if the +// bidder wins. At least one seatbid object is required, which contains at least +// one bid for an impression. Other attributes are optional. +// +// To express a "no-bid", the options are to return an empty response with HTTP +// 204. Alternately if the bidder wishes to convey to the exchange a reason for +// not bidding, just a BidResponse object is returned with a reason code in the +// nbr attribute. +type BidResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // ID of the bid request to which this is a response. + // REQUIRED by the OpenRTB specification. + Id *string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` + // Array of seatbid objects; 1+ required if a bid is to be made. + Seatbid []*BidResponse_SeatBid `protobuf:"bytes,2,rep,name=seatbid" json:"seatbid,omitempty"` + // Bidder generated response ID to assist with logging/tracking. + Bidid *string `protobuf:"bytes,3,opt,name=bidid" json:"bidid,omitempty"` + // Bid currency using ISO-4217 alpha codes. + Cur *string `protobuf:"bytes,4,opt,name=cur" json:"cur,omitempty"` + // Optional feature to allow a bidder to set data in the exchange's cookie. + // The string must be in base85 cookie safe characters and be in any format. + // Proper JSON encoding must be used to include "escaped" quotation marks. + Customdata *string `protobuf:"bytes,5,opt,name=customdata" json:"customdata,omitempty"` + // Reason for not bidding. + // Refer to enum com.iabtechlab.openrtb.v3.NoBidReason for values. + Nbr *int32 `protobuf:"varint,6,opt,name=nbr" json:"nbr,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BidResponse) Reset() { + *x = BidResponse{} + mi := &file_com_iabtechlab_openrtb_v2_openrtb_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BidResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BidResponse) ProtoMessage() {} + +func (x *BidResponse) ProtoReflect() protoreflect.Message { + mi := &file_com_iabtechlab_openrtb_v2_openrtb_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BidResponse.ProtoReflect.Descriptor instead. +func (*BidResponse) Descriptor() ([]byte, []int) { + return file_com_iabtechlab_openrtb_v2_openrtb_proto_rawDescGZIP(), []int{1} +} + +func (x *BidResponse) GetId() string { + if x != nil && x.Id != nil { + return *x.Id + } + return "" +} + +func (x *BidResponse) GetSeatbid() []*BidResponse_SeatBid { + if x != nil { + return x.Seatbid + } + return nil +} + +func (x *BidResponse) GetBidid() string { + if x != nil && x.Bidid != nil { + return *x.Bidid + } + return "" +} + +func (x *BidResponse) GetCur() string { + if x != nil && x.Cur != nil { + return *x.Cur + } + return "" +} + +func (x *BidResponse) GetCustomdata() string { + if x != nil && x.Customdata != nil { + return *x.Customdata + } + return "" +} + +func (x *BidResponse) GetNbr() int32 { + if x != nil && x.Nbr != nil { + return *x.Nbr + } + return 0 +} + +// The Native Object defines the native advertising opportunity available for +// bid via this bid request. It must be included directly in the impression +// object if the impression offered for auction is a native ad format. +// +// Note: Prior to VERSION 1.1, the specification could be interpreted as +// requiring the native request to have a root node with a single field "native" +// that would contain the NativeRequest as its value. In 1.2 the NativeRequest +// Object specified here is now the root object. +type NativeRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Version of the Native Markup version in use. + Ver *string `protobuf:"bytes,1,opt,name=ver" json:"ver,omitempty"` + // The Layout ID of the native ad unit. + // Refer to enum LayoutId for values. + // RECOMMENDED by OpenRTB Native 1.0; optional in 1.1, DEPRECATED in 1.2. + Layout *int32 `protobuf:"varint,2,opt,name=layout" json:"layout,omitempty"` + // The Ad unit ID of the native ad unit. This corresponds to one of IAB Core-6 + // native ad units. + // Refer to enum AdUnitId for values. + // RECOMMENDED by OpenRTB Native 1.0; optional in 1.1, DEPRECATED in 1.2. + Adunit *int32 `protobuf:"varint,3,opt,name=adunit" json:"adunit,omitempty"` + // The context in which the ad appears. + // Refer to enum ContextType for values. + // RECOMMENDED in 1.2. + Context *int32 `protobuf:"varint,7,opt,name=context" json:"context,omitempty"` + // A more detailed context in which the ad appears. + // Refer to enum com.iabtechlab.adcom.v1.enums.DisplayContextType for values. + Contextsubtype *int32 `protobuf:"varint,8,opt,name=contextsubtype" json:"contextsubtype,omitempty"` + // The design/format/layout of the ad unit being offered. + // Refer to enum com.iabtechlab.adcom.v1.enums.DisplayPlacementType for + // values. + // RECOMMENDED by the OpenRTB Native specification. + Plcmttype *int32 `protobuf:"varint,9,opt,name=plcmttype" json:"plcmttype,omitempty"` + // The number of identical placements in this Layout. + Plcmtcnt *int32 `protobuf:"varint,4,opt,name=plcmtcnt,def=1" json:"plcmtcnt,omitempty"` + // 0 for the first ad, 1 for the second ad, and so on. Note this would + // generally NOT be used in combination with plcmtcnt - either you are + // auctioning multiple identical placements (in which case plcmtcnt>1, seq=0) + // or you are holding separate auctions for distinct items in the feed (in + // which case plcmtcnt=1, seq>=1). + Seq *int32 `protobuf:"varint,5,opt,name=seq,def=0" json:"seq,omitempty"` + // Any bid must comply with the array of elements expressed by the Exchange. + // REQUIRED by the OpenRTB Native specification: at least 1 element. + Assets []*NativeRequest_Asset `protobuf:"bytes,6,rep,name=assets" json:"assets,omitempty"` + // Whether the supply source / impression supports returning an assetsurl + // instead of an asset object. 0 or the absence of the field indicates no such + // support. + Aurlsupport *bool `protobuf:"varint,11,opt,name=aurlsupport" json:"aurlsupport,omitempty"` + // Whether the supply source / impression supports returning a DCO URL instead + // of an asset object. 0 or the absence of the field indicates no such + // support. Beta feature. + Durlsupport *bool `protobuf:"varint,12,opt,name=durlsupport" json:"durlsupport,omitempty"` + // Specifies what type of event tracking is supported. + Eventtrackers []*NativeRequest_EventTrackers `protobuf:"bytes,13,rep,name=eventtrackers" json:"eventtrackers,omitempty"` + // Set to 1 when the native ad supports buyer-specific privacy notice. Set to + // 0 (or field absent) when the native ad doesn't support custom privacy links + // or if support is unknown. + // RECOMMENDED and implemented in 1.2 + Privacy *bool `protobuf:"varint,14,opt,name=privacy" json:"privacy,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +// Default values for NativeRequest fields. +const ( + Default_NativeRequest_Plcmtcnt = int32(1) + Default_NativeRequest_Seq = int32(0) +) + +func (x *NativeRequest) Reset() { + *x = NativeRequest{} + mi := &file_com_iabtechlab_openrtb_v2_openrtb_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NativeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NativeRequest) ProtoMessage() {} + +func (x *NativeRequest) ProtoReflect() protoreflect.Message { + mi := &file_com_iabtechlab_openrtb_v2_openrtb_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NativeRequest.ProtoReflect.Descriptor instead. +func (*NativeRequest) Descriptor() ([]byte, []int) { + return file_com_iabtechlab_openrtb_v2_openrtb_proto_rawDescGZIP(), []int{2} +} + +func (x *NativeRequest) GetVer() string { + if x != nil && x.Ver != nil { + return *x.Ver + } + return "" +} + +func (x *NativeRequest) GetLayout() int32 { + if x != nil && x.Layout != nil { + return *x.Layout + } + return 0 +} + +func (x *NativeRequest) GetAdunit() int32 { + if x != nil && x.Adunit != nil { + return *x.Adunit + } + return 0 +} + +func (x *NativeRequest) GetContext() int32 { + if x != nil && x.Context != nil { + return *x.Context + } + return 0 +} + +func (x *NativeRequest) GetContextsubtype() int32 { + if x != nil && x.Contextsubtype != nil { + return *x.Contextsubtype + } + return 0 +} + +func (x *NativeRequest) GetPlcmttype() int32 { + if x != nil && x.Plcmttype != nil { + return *x.Plcmttype + } + return 0 +} + +func (x *NativeRequest) GetPlcmtcnt() int32 { + if x != nil && x.Plcmtcnt != nil { + return *x.Plcmtcnt + } + return Default_NativeRequest_Plcmtcnt +} + +func (x *NativeRequest) GetSeq() int32 { + if x != nil && x.Seq != nil { + return *x.Seq + } + return Default_NativeRequest_Seq +} + +func (x *NativeRequest) GetAssets() []*NativeRequest_Asset { + if x != nil { + return x.Assets + } + return nil +} + +func (x *NativeRequest) GetAurlsupport() bool { + if x != nil && x.Aurlsupport != nil { + return *x.Aurlsupport + } + return false +} + +func (x *NativeRequest) GetDurlsupport() bool { + if x != nil && x.Durlsupport != nil { + return *x.Durlsupport + } + return false +} + +func (x *NativeRequest) GetEventtrackers() []*NativeRequest_EventTrackers { + if x != nil { + return x.Eventtrackers + } + return nil +} + +func (x *NativeRequest) GetPrivacy() bool { + if x != nil && x.Privacy != nil { + return *x.Privacy + } + return false +} + +// The native response object is the top level JSON object which identifies an +// native response. Note: Prior to VERSION 1.1, the native response's root node +// was an object with a single field "native" that would contain the object +// above as its value. In 1.2 the NativeResponse Object specified here is now +// the root object. +type NativeResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Version of the Native Markup version in use. + // RECOMMENDED in 1.2 + Ver *string `protobuf:"bytes,1,opt,name=ver" json:"ver,omitempty"` + // List of native ad's assets. + // RECOMMENDED in 1.0, 1.1, or in 1.2 as a fallback if assetsurl is provided. + // REQUIRED in 1.2, if not assetsurl is provided. + Assets []*NativeResponse_Asset `protobuf:"bytes,2,rep,name=assets" json:"assets,omitempty"` + // URL of alternate source for the assets object. The expected response is a + // JSON object mirroring the asset object in the bid response, subject to + // certain requirements as specified in the individual objects. Where present, + // overrides the assets object in the response. + Assetsurl *string `protobuf:"bytes,6,opt,name=assetsurl" json:"assetsurl,omitempty"` + // URL where a dynamic creative specification may be found for populating this + // ad, per the Dynamic Content Ads Specification. Note this is a beta option + // as the interpretation of the Dynamic Content Ads Specification and how to + // assign those elementes into a native ad is outside the scope of this spec + // and must be agreed offline between parties or as may be specified in a + // future revision of the Dynamic Content Ads spec. Where present, overrides + // the assets object in the response. + Dcourl *string `protobuf:"bytes,7,opt,name=dcourl" json:"dcourl,omitempty"` + // Destination Link. This is default link object for the ad. Individual assets + // can also have a link object which applies if the asset is activated + // (clicked). If the asset doesn't have a link object, the parent link object + // applies. See ResponseLink definition. + // REQUIRED by the OpenRTB Native specification. + Link *NativeResponse_Link `protobuf:"bytes,3,opt,name=link" json:"link,omitempty"` + // Array of impression tracking URLs, expected to return a 1x1 image or 204 + // response - typically only passed when using 3rd party trackers. To be + // deprecated in 1.2 - Replaced with EventTracker. + Imptrackers []string `protobuf:"bytes,4,rep,name=imptrackers" json:"imptrackers,omitempty"` + // Optional javascript impression tracker. Contains