Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,9 @@
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["adcp-sdk-java-tools"]
"ignore": [],
"privatePackages": {
"version": true,
"tag": false
}
}
15 changes: 15 additions & 0 deletions .changeset/negotiation-proposal-apis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"adcp": minor
"adcp-server": minor
"adcp-testing": minor
"adcp-reactor": minor
"adcp-mutiny": minor
"adcp-kotlin": minor
"adcp-cli": minor
---

feat(negotiation): add first-class buyer and seller proposal APIs for AdCP 3.2

Introduces the `negotiation` package with sealed outcome models, capability-aware
request builders, terms digest verification (RFC 8785 JCS), response verification
utilities, and server-side handler interface for `refine_proposals`.
1 change: 1 addition & 0 deletions adcp-cli/gradle.lockfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ com.google.errorprone:error_prone_annotations:2.48.0=compileClasspath,runtimeCla
com.google.protobuf:protobuf-java-util:4.33.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java:4.33.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.networknt:json-schema-validator:1.5.6=runtimeClasspath,testRuntimeClasspath
io.github.erdtman:java-json-canonicalization:1.1=runtimeClasspath,testRuntimeClasspath
io.modelcontextprotocol.sdk:mcp-core:1.1.2=runtimeClasspath,testRuntimeClasspath
io.modelcontextprotocol.sdk:mcp-json-jackson2:1.1.2=runtimeClasspath,testRuntimeClasspath
io.projectreactor:reactor-core:3.7.0=runtimeClasspath,testRuntimeClasspath
Expand Down
5 changes: 5 additions & 0 deletions adcp-cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "adcp-cli",
"version": "0.1.0",
"private": true
}
115 changes: 111 additions & 4 deletions adcp-cli/src/main/java/org/adcontextprotocol/adcp/cli/Main.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
package org.adcontextprotocol.adcp.cli;

import com.fasterxml.jackson.databind.JsonNode;
import org.adcontextprotocol.adcp.AdcpClient;
import org.adcontextprotocol.adcp.AgentConfig;
import org.adcontextprotocol.adcp.http.SsrfPolicy;
import org.adcontextprotocol.adcp.negotiation.ProposalRefinement;
import org.adcontextprotocol.adcp.negotiation.RefineProposalsRequest;
import org.adcontextprotocol.adcp.negotiation.RefineProposalsResponse;
import org.adcontextprotocol.adcp.negotiation.RefinementConstraints;
import org.adcontextprotocol.adcp.negotiation.ResponseVerifier;
import org.adcontextprotocol.adcp.negotiation.TotalBudgetConstraint;

import java.math.BigDecimal;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
* Entry point for the {@code adcp} CLI. Commands land here as the CLI track
* is implemented; today this is a placeholder that prints a usage stub.
* Entry point for the {@code adcp} CLI and runnable proposal-negotiation lab.
*/
public final class Main {

Expand All @@ -11,7 +27,98 @@ private Main() {
}

public static void main(String[] args) {
System.out.println("adcp <not yet implemented>");
System.out.println("see ROADMAP.md track 13 (cli) for the planned surface");
if (args.length > 0 && "proposal-negotiation".equals(args[0])) {
runProposalNegotiation(args);
return;
}
System.out.println("adcp proposal-negotiation [seller-mcp-url]");
System.out.println("Set ADCP_AUTH_TOKEN before running the public training-seller lab.");
}

private static void runProposalNegotiation(String[] args) {
String endpoint = args.length > 1 ? args[1]
: "https://test-agent.adcontextprotocol.org/sales/profiles/constrained-seller/mcp";
String token = System.getenv("ADCP_AUTH_TOKEN");
if (token == null || token.isBlank()) {
throw new IllegalStateException("Set ADCP_AUTH_TOKEN to the public test token");
}

String nonce = UUID.randomUUID().toString();
AgentConfig agent = AgentConfig.mcp("training-seller", URI.create(endpoint), token);
try (AdcpClient client = AdcpClient.builder()
.agent(agent)
.ssrfPolicy(SsrfPolicy.strict())
.build()) {
Map<String, Object> version = Map.of(
"adcp_version", "3.2-beta.6", "adcp_major_version", 3);
JsonNode capabilities = client.callTool(
"get_adcp_capabilities", version, JsonNode.class);
if (!capabilities.path("media_buy").path("lifecycle_tools")
.toString().contains("refine_proposals")) {
throw new IllegalStateException("seller did not advertise refine_proposals");
}

Map<String, Object> proposalArgs = new java.util.LinkedHashMap<>(version);
proposalArgs.put("idempotency_key", "java-request-" + nonce);
proposalArgs.put("brand", Map.of("domain", "acmeoutdoor.example"));
proposalArgs.put("brief", "social engagement display");
JsonNode proposals = client.callTool(
"request_proposals", proposalArgs, JsonNode.class);
String sourceId = proposals.path("proposals").path(0)
.path("proposal_id").asText(null);
if (sourceId == null) {
throw new IllegalStateException("seller returned no source proposal");
}

RefinementConstraints constraints = new RefinementConstraints(
new TotalBudgetConstraint(null, new BigDecimal("50000"), "USD"),
null, null, null);
RefineProposalsRequest three = request(
sourceId, constraints, 3, "java-refine-three-" + nonce);
RefineProposalsResponse partial = client.refineProposals(three);
requireVerified(three, partial);

// This changes the logical request, so it deliberately uses a new key.
RefineProposalsRequest two = request(
sourceId, constraints, 2, "java-refine-two-" + nonce);
RefineProposalsResponse revised = client.refineProposals(two);
requireVerified(two, revised);

System.out.printf("first=%s (%d alternatives), retry=%s (%d alternatives)%n",
partial.results().getFirst().outcome(), proposalCount(partial),
revised.results().getFirst().outcome(), proposalCount(revised));
}
}

private static RefineProposalsRequest request(
String sourceId, RefinementConstraints constraints, int alternatives, String key) {
return RefineProposalsRequest.builder()
.adcpVersion("3.2-beta.6")
.adcpMajorVersion(3)
.idempotencyKey(key)
.maxAlternatives(3)
.addRefinement(ProposalRefinement.builder(sourceId)
.constraints(constraints)
.alternatives(alternatives)
.build())
.build();
}

private static void requireVerified(RefineProposalsRequest request,
RefineProposalsResponse response) {
List<String> violations = ResponseVerifier.verify(request, response);
if (!violations.isEmpty()) {
throw new IllegalStateException("invalid seller response: " + violations);
}
}

private static int proposalCount(RefineProposalsResponse response) {
return switch (response.results().getFirst()) {
case org.adcontextprotocol.adcp.negotiation.RefinementResult.Revised r ->
r.proposals().size();
case org.adcontextprotocol.adcp.negotiation.RefinementResult.Partial p ->
p.proposals().size();
default -> 0;
};
}
}
1 change: 1 addition & 0 deletions adcp-kotlin/gradle.lockfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ com.google.errorprone:error_prone_annotations:2.48.0=apiDependenciesMetadata,com
com.google.protobuf:protobuf-java-util:4.33.2=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.google.protobuf:protobuf-java:4.33.2=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.networknt:json-schema-validator:1.5.6=runtimeClasspath,testRuntimeClasspath
io.github.erdtman:java-json-canonicalization:1.1=runtimeClasspath,testRuntimeClasspath
io.modelcontextprotocol.sdk:mcp-core:1.1.2=runtimeClasspath,testRuntimeClasspath
io.modelcontextprotocol.sdk:mcp-json-jackson2:1.1.2=runtimeClasspath,testRuntimeClasspath
io.projectreactor:reactor-core:3.7.0=runtimeClasspath,testRuntimeClasspath
Expand Down
5 changes: 5 additions & 0 deletions adcp-kotlin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "adcp-kotlin",
"version": "0.1.0",
"private": true
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,16 @@
// Nullability is correct because the Java surface is JSpecify-annotated.

package org.adcontextprotocol.adcp.kotlin

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.adcontextprotocol.adcp.AdcpClient
import org.adcontextprotocol.adcp.negotiation.RefineProposalsRequest
import org.adcontextprotocol.adcp.negotiation.RefineProposalsResponse

/** Coroutine bridge preserving task errors and sealed per-proposal outcomes. */
public suspend fun AdcpClient.refineProposalsAwait(
request: RefineProposalsRequest,
): RefineProposalsResponse = withContext(Dispatchers.IO) {
refineProposals(request)
}
1 change: 1 addition & 0 deletions adcp-mutiny/gradle.lockfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ com.google.errorprone:error_prone_annotations:2.48.0=compileClasspath,runtimeCla
com.google.protobuf:protobuf-java-util:4.33.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java:4.33.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.networknt:json-schema-validator:1.5.6=runtimeClasspath,testRuntimeClasspath
io.github.erdtman:java-json-canonicalization:1.1=runtimeClasspath,testRuntimeClasspath
io.modelcontextprotocol.sdk:mcp-core:1.1.2=runtimeClasspath,testRuntimeClasspath
io.modelcontextprotocol.sdk:mcp-json-jackson2:1.1.2=runtimeClasspath,testRuntimeClasspath
io.projectreactor:reactor-core:3.7.0=runtimeClasspath,testRuntimeClasspath
Expand Down
5 changes: 5 additions & 0 deletions adcp-mutiny/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "adcp-mutiny",
"version": "0.1.0",
"private": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.adcontextprotocol.adcp.mutiny;

import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.infrastructure.Infrastructure;
import org.adcontextprotocol.adcp.AdcpClient;
import org.adcontextprotocol.adcp.negotiation.RefineProposalsRequest;
import org.adcontextprotocol.adcp.negotiation.RefineProposalsResponse;

import java.util.Objects;

/** Non-blocking SmallRye Mutiny bridge for the synchronous AdCP client. */
public final class MutinyAdcpClient {

private final AdcpClient delegate;

public MutinyAdcpClient(AdcpClient delegate) {
this.delegate = Objects.requireNonNull(delegate);
}

/**
* Refines proposals on Mutiny's default worker pool. Task-level failures
* fail the {@link Uni}; per-proposal outcomes remain in the typed response.
*/
public Uni<RefineProposalsResponse> refineProposals(RefineProposalsRequest request) {
return Uni.createFrom().item(() -> delegate.refineProposals(request))
.runSubscriptionOn(Infrastructure.getDefaultWorkerPool());
}
}
1 change: 1 addition & 0 deletions adcp-reactor/gradle.lockfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ com.google.errorprone:error_prone_annotations:2.48.0=compileClasspath,runtimeCla
com.google.protobuf:protobuf-java-util:4.33.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java:4.33.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.networknt:json-schema-validator:1.5.6=runtimeClasspath,testRuntimeClasspath
io.github.erdtman:java-json-canonicalization:1.1=runtimeClasspath,testRuntimeClasspath
io.modelcontextprotocol.sdk:mcp-core:1.1.2=runtimeClasspath,testRuntimeClasspath
io.modelcontextprotocol.sdk:mcp-json-jackson2:1.1.2=runtimeClasspath,testRuntimeClasspath
io.projectreactor:reactor-core:3.7.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
Expand Down
5 changes: 5 additions & 0 deletions adcp-reactor/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "adcp-reactor",
"version": "0.1.0",
"private": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.adcontextprotocol.adcp.reactor;

import org.adcontextprotocol.adcp.AdcpClient;
import org.adcontextprotocol.adcp.negotiation.RefineProposalsRequest;
import org.adcontextprotocol.adcp.negotiation.RefineProposalsResponse;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.util.Objects;

/** Non-blocking Project Reactor bridge for the synchronous AdCP client. */
public final class ReactorAdcpClient {

private final AdcpClient delegate;

public ReactorAdcpClient(AdcpClient delegate) {
this.delegate = Objects.requireNonNull(delegate);
}

/**
* Refines proposals on Reactor's bounded-elastic scheduler.
* Transport and task-level errors are emitted through {@link Mono#error};
* per-proposal outcomes remain safely discriminated in the response.
*/
public Mono<RefineProposalsResponse> refineProposals(RefineProposalsRequest request) {
return Mono.fromCallable(() -> delegate.refineProposals(request))
.subscribeOn(Schedulers.boundedElastic());
}
}
1 change: 1 addition & 0 deletions adcp-server/gradle.lockfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ com.google.errorprone:error_prone_annotations:2.48.0=compileClasspath,runtimeCla
com.google.protobuf:protobuf-java-util:4.33.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java:4.33.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.networknt:json-schema-validator:2.0.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.github.erdtman:java-json-canonicalization:1.1=runtimeClasspath,testRuntimeClasspath
io.modelcontextprotocol.sdk:mcp-core:1.1.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.modelcontextprotocol.sdk:mcp-json-jackson2:1.1.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.projectreactor:reactor-core:3.7.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
Expand Down
5 changes: 5 additions & 0 deletions adcp-server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "adcp-server",
"version": "0.1.0",
"private": true
}
Loading
Loading