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
138 changes: 117 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,43 @@
[![Java](https://img.shields.io/badge/Java-17%2B-ED8B00?logo=openjdk&logoColor=white)](https://openjdk.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)

Official Java software development kits for the
Official Java software development kit for the
[Offering Discovery Protocol](https://www.offeringprotocol.org/), the open protocol for discovering
Services and navigating their Offerings.

ODP separates Service discovery from catalog discovery. An Agent searches the canonical directory
for candidate Services, inspects each Service's live ODP document, and then navigates or searches
that Service's Collections and Offerings.
that Service's Collections and Offerings. Full Offering details can describe structured attributes,
price previews, images, and executable Actions without forcing every industry into one catalog
schema.

## Modules
## Start here

| Module | Responsibility |
| --------------------------------------------- | --------------------------------------------------------------------- |
| [`odp-core`](./odp-core/README.md) | Protocol models, validation, identity, references, and pagination |
| [`odp-directory`](./odp-directory/README.md) | Canonical production and sandbox directory client |
| [`odp-agent`](./odp-agent/README.md) | Directory-to-Service discovery and Agent-oriented catalog workflows |
| [`odp-service`](./odp-service/README.md) | Service document, catalog operations, and integration helpers |
Choose the module that matches the role your application implements:

| I am building... | Start with | Responsibility |
| -------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------- |
| An Agent, command-line tool, or automation | [`odp-agent`](./odp-agent/README.md) | Directory-to-Service discovery and live catalog navigation |
| An ODP Service | [`odp-service`](./odp-service/README.md) | Service document, fixed routes, static or storage-backed operations |
| A directory-only integration | [`odp-directory`](./odp-directory/README.md) | Canonical production or sandbox Service search |
| An ODP validator or protocol implementation | [`odp-core`](./odp-core/README.md) | Models, bundled schemas, identity, references, and pagination |

All artifacts use Maven group `org.offeringprotocol`, require Java 17 or newer, and are available
from Maven Central without adding a repository.

Dependencies flow from role modules toward `odp-core`; `odp-agent` composes `odp-directory`.
`odp-core` does not depend on another ODP module, and `odp-service` does not depend on Agent or
directory behavior.

All artifacts use the Maven group `org.offeringprotocol` and require Java 17 or newer.

## Installation

Applications should depend on the module matching their role. Maven resolves its required ODP
modules transitively.

For an Agent application:

```xml
<dependency>
<groupId>org.offeringprotocol</groupId>
<artifactId>odp-agent</artifactId>
<version>0.1.0</version>
<version>0.1.1</version>
</dependency>
```

Expand All @@ -49,17 +51,110 @@ For a Service integration:
<dependency>
<groupId>org.offeringprotocol</groupId>
<artifactId>odp-service</artifactId>
<version>0.1.0</version>
<version>0.1.1</version>
</dependency>
```

Gradle applications use the same coordinates:
Gradle uses the same coordinates:

```kotlin
implementation("org.offeringprotocol:odp-agent:0.1.0")
implementation("org.offeringprotocol:odp-agent:0.1.1")
```

Maven resolves the required Core and Directory modules transitively. Applications should not add
every ODP module to one project unless they actually implement multiple roles.

## Agent quick start

`OdpAgent` performs two-stage discovery: it searches the canonical directory and then searches the
live catalogs of matching Services. A Service failure becomes an `IssueEvent` without discarding
Offerings returned by other Services.

```java
import org.offeringprotocol.odp.agent.OdpAgent;
import org.offeringprotocol.odp.directory.DirectoryClient;

DirectoryClient directory = DirectoryClient.create();
OdpAgent agent = new OdpAgent(directory);

for (OdpAgent.DiscoveryEvent event : agent.searchOfferings("plants", 10, 10)) {
if (event instanceof OdpAgent.OfferingEvent offering) {
System.out.printf("%s: %s%n", offering.service().name(), offering.offering().name());
} else if (event instanceof OdpAgent.IssueEvent issue) {
System.err.printf("%s: %s%n", issue.service().serviceOrigin(), issue.message());
}
}
```

## Examples
For a known Service, `OdpServiceClient` inspects `/.well-known/odp`, exposes the advertised
operations, and provides Collection and Offering list, search, get, and continuation methods. See
the [Agent integration guide](./odp-agent/README.md) for direct navigation, sandbox selection,
localization, pagination, and authenticated transport composition.

## Service quick start

The minimum Service integration publishes `/.well-known/odp`, lists Offerings, and retrieves one
Offering. `StaticCatalog` provides those operations for a small in-memory catalog.

```java
import java.util.List;
import org.offeringprotocol.odp.core.OdpJson;
import org.offeringprotocol.odp.core.Offering;
import org.offeringprotocol.odp.service.OdpService;
import org.offeringprotocol.odp.service.StaticCatalog;

Offering offering = OdpJson.parseOffering("""
{
"odp_version": "1.0",
"id": "rubber-plant",
"name": "Rubber Plant",
"description": "A resilient indoor plant."
}
""");

OdpService service = OdpService.builder(
"Example Plant Store",
"Indoor plants selected for homes and offices.",
"en",
"/odp")
.keywords(List.of("plants", "indoor-plants"))
.endpoints(StaticCatalog.create(List.of(offering), List.of()))
.build();
```

Adapt the framework's incoming request to `OdpHttpRequest`, pass it to `service.handle(...)`, and
write the returned `OdpHttpResponse`. Large catalogs provide handlers backed by their own storage
and indexes instead of materializing the catalog in memory. See the
[Service integration guide](./odp-service/README.md) and the
[runnable small Service](./examples/README.md#small-service).

## Protocol composition

ODP advertises AEP enrollment, operation authentication requirements, MPP and x402 payment
support, and Offering Actions. It does not duplicate those protocols' credential or payment
semantics.

The default Java Agent transport performs anonymous HTTP requests. Applications inject an
`OdpTransport` when catalog requests need AEP credentials, MPP, x402, or application-specific
network policy. The Service runtime advertises authentication requirements but expects the hosting
application to enforce authentication and payment before or around the ODP handler.

An Offering may describe an Action, but the Java SDK never invokes an Action implicitly. The
application selects the Action and remains responsible for user approval, authentication, payment,
and state-changing requests.

## Runtime boundaries

Applications own persistent caching, authentication context, authorization, catalog persistence,
indexing, rate limiting, and Action execution. The clients enforce ODP document validation,
same-origin redirect and continuation rules, response-size limits, and fixed production or sandbox
directory selection.

`OdpServiceClient` fetches and validates its Service Document when the client is created and retains
that inspection for the client's lifetime. The Java SDK does not maintain a persistent cache or
refresh a live client automatically; applications choose when to reuse or recreate clients.

## Runnable examples

Run the small Service and Agent examples in separate terminals:

Expand All @@ -70,7 +165,7 @@ Run the small Service and Agent examples in separate terminals:

The Agent example explicitly uses a mock directory assembled from reachable Service origins. It
then performs live Service inspection, Offering listing, and full Offering retrieval. See
[examples/README.md](./examples/README.md) for the complete walkthrough.
[examples/README.md](./examples/README.md) for the walkthrough and source map.

## Development

Expand Down Expand Up @@ -112,7 +207,8 @@ examples, and test vectors.

## Security

See [SECURITY.md](./SECURITY.md) for vulnerability reporting.
See [SECURITY.md](./SECURITY.md) for vulnerability reporting. The examples use illustrative
in-memory catalogs and an explicitly labeled mock directory.

## License

Expand Down
85 changes: 76 additions & 9 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,101 @@
# Runnable examples

The examples demonstrate the two ODP integration roles with the public Java modules.
The examples demonstrate the two ODP integration roles with the same public Java modules published
to Maven Central. Run all commands from the repository root with Java 17 or newer.

## Source map

| Source | Purpose |
| ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| [`SmallService.java`](./src/main/java/org/offeringprotocol/odp/examples/SmallService.java) | Framework adapter, Service builder, static catalog, and search |
| [`AgentDiscovery.java`](./src/main/java/org/offeringprotocol/odp/examples/AgentDiscovery.java) | Inspection, terse listing, and full Offering retrieval |
| [`MockDirectory.java`](./src/main/java/org/offeringprotocol/odp/examples/MockDirectory.java) | Clearly isolated local stand-in for candidate Service discovery |

## Small Service

The small Service keeps a Collection and two Offerings in memory. It publishes a Service Document,
supports listing and searching Offerings, returns full Offering details, and exposes a free download
Action.
The small Service keeps one Collection and two Offerings in memory. It publishes a Service
Document, provides the required Offering list and detail operations, adds Offering search, and
exposes a free download Action.

```sh
./scripts/run-small-service.sh
```

The Service listens on `http://127.0.0.1:4103` by default. Set `PORT` to use another port.
The Service listens on `http://127.0.0.1:4103` by default. Set `PORT` to use another port:

```sh
PORT=4203 ./scripts/run-small-service.sh
```

At startup it prints the URLs an integrator needs:

```text
Small ODP Service listening at http://127.0.0.1:4103
Service Document: http://127.0.0.1:4103/.well-known/odp
Offerings: http://127.0.0.1:4103/odp/offerings
```

Every request prints its method, path, and response status. Stop the process with Control-C.

The example uses the JDK `HttpServer` only as a transparent adapter. A Spring, Jakarta, Netty, or
other integration performs the same conversion into `OdpHttpRequest` and writes the returned
`OdpHttpResponse` through its own response API.

## Agent discovery

The Agent example composes a mock directory from reachable local Services, prints every inspected
Service Document, lists terse Offerings, and retrieves each full Offering. The mock directory is
example infrastructure; it does not call the canonical ODP directory.
Service Document, lists terse Offerings, and retrieves each full Offering.

Start the small Service in another terminal, then run:

```sh
./scripts/run-agent-example.sh
```

Without arguments, the mock directory checks ports 4101, 4102, and 4103. Pass one or more Service
origins to use a different set:
Without arguments, the mock directory checks ports 4101, 4102, and 4103 and includes only reachable
ODP Services. Pass one or more Service origins to use an explicit set:

```sh
./scripts/run-agent-example.sh https://service.example
```

The mock directory is example infrastructure. It does not call or imitate the canonical directory
API. Production Agent applications use `DirectoryClient.create()` or construct `OdpAgent` with a
production or sandbox Directory client.

The output separates each discovery stage:

```text
Mock directory contains 1 reachable ODP Service(s).

ODP Service Document:
{...}

Terse Offering list:
{...}

Full Offering agent-guide:
{...}
```

## From example to application

For an Agent integration:

1. Replace `MockDirectory` with the canonical `DirectoryClient`.
2. Choose production or sandbox explicitly when constructing the directory client.
3. Use `OdpServiceClient` for each selected Service and check its advertised operations.
4. Supply an `OdpTransport` when ODP requests require AEP, MPP, x402, or application credentials.
5. Apply application-specific caching, result ceilings, retries, and observability.

For a Service integration:

1. Map the application's catalog data into ODP `Offering` and optional `Collection` records.
2. Use `StaticCatalog` only when the complete catalog is appropriate for an immutable memory
snapshot; otherwise provide storage-backed handlers.
3. Mount `OdpService` at the well-known path and configured endpoint base.
4. Enforce authentication, payment, authorization, and rate limits in the hosting HTTP stack.
5. Publish appropriate cache metadata and operational telemetry through the host application.

See the [Agent guide](../odp-agent/README.md) and
[Service guide](../odp-service/README.md) for the complete public API boundaries.
2 changes: 1 addition & 1 deletion examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.offeringprotocol</groupId>
<artifactId>odp-java</artifactId>
<version>0.1.0</version>
<version>0.1.1</version>
</parent>

<artifactId>odp-examples</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import org.offeringprotocol.odp.core.OdpOperation;
import org.offeringprotocol.odp.core.Offering;
import org.offeringprotocol.odp.core.Page;
import org.offeringprotocol.odp.core.ServiceDocument;
import org.offeringprotocol.odp.service.OdpHttpRequest;
import org.offeringprotocol.odp.service.OdpHttpResponse;
import org.offeringprotocol.odp.service.OdpService;
Expand Down Expand Up @@ -47,7 +46,11 @@ public static void main(String[] arguments) throws IOException {
.toList();
return new Page<>(null, Odp.VERSION, matches, null, Map.of());
}));
OdpService service = new OdpService(document(), endpoints);
OdpService service = OdpService.builder(
"ODP Developer Resources", "Free resources for ODP integrators", "en", "/odp")
.keywords(List.of("agent", "developer", "documentation"))
.endpoints(endpoints)
.build();
HttpServer server = HttpServer.create(
new InetSocketAddress("127.0.0.1", port), // NOPMD - The example must remain local-only.
0);
Expand Down Expand Up @@ -101,28 +104,6 @@ private static Map<String, List<String>> query(String rawQuery) {
return result;
}

private static ServiceDocument document() {
return new ServiceDocument(
Odp.VERSION,
"ODP Developer Resources",
"Free resources for ODP integrators",
null,
"en",
List.of("en"),
null,
List.of("agent", "developer", "documentation"),
null,
null,
new ServiceDocument.Http("/odp", null),
null,
null,
null,
null,
null,
null,
Map.of());
}

private static Collection collection() {
return new Collection(
null,
Expand Down
Loading