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
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ children := odp.CollectionSearchRequest{ODPVersion: odp.Version, ParentID: odp.S

## Directory discovery

Package `directory` searches candidate Services through the canonical production directory or its
fixed sandbox environment. It validates cached Service summaries, follows opaque same-origin
continuations, exposes structured facets, and provides keyword suggestions.
Package `directory` searches indexed Services and Collections through the canonical production
directory or its fixed sandbox environment. It validates results, follows opaque same-origin
continuations when offered, exposes structured facets, and provides search suggestions.

```go
directoryClient, err := directory.New(directory.Options{})
Expand All @@ -127,15 +127,21 @@ for candidate, err := range directoryClient.SearchServices(ctx, directory.Search
Options: []odp.PaymentOption{odp.PaymentOptionInflow, odp.PaymentOptionSolana},
}},
},
}, directory.IterationOptions{MaxItems: 20}) {
}, directory.IterationOptions{MaxItems: 20}).Items {
if err != nil {
return err
}
inspect(candidate.ServiceOrigin)
}
```

See the [directory package guide](./directory/README.md) for page traversal, suggestions, and
Use `Search` and `ContinueSearch` for mixed Service/Collection results, or `SearchServices` and
`ContinueSearchServices` for Service-only discovery. Each returns independent `Items` and
`Responses` iterators. `Suggest` returns matching target names; `SuggestServices` provides
Service-only keyword suggestions. Mixed search is capped at 100 results without continuation;
refine the query rather than assuming every match was returned.

See the [directory package guide](./directory/README.md) for response traversal, migration, suggestions, and
sandbox usage.

## Agent integration
Expand Down
5 changes: 5 additions & 0 deletions agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,11 @@ Agent processing. Recognized descriptors remain subject to current-version valid
See the [runnable Agent example](../examples/odp-agent-discovery/README.md), which clearly labels and
isolates its mock directory while querying live ODP Services.

For discovery that includes submitted Collections, call the Directory client's `Search` and
iterate its `Items`. A Collection result identifies its owning Service and remote Collection ID.
Inspect that Service, then call `GetCollection` with the ID. `SearchOfferingsAcrossServices`
continues to select Services only; it does not treat Collections as separate Services.

## Related documentation

- [Directory integration](../directory/README.md)
Expand Down
2 changes: 1 addition & 1 deletion agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (agent *Agent) searchOfferingsAcrossServices(ctx context.Context, request F
return
}
services := make([]directory.Service, 0, maxServices)
for service, err := range agent.directory.SearchServices(ctx, request.Services, directory.IterationOptions{MaxItems: maxServices}) {
for service, err := range agent.directory.SearchServices(ctx, request.Services, directory.IterationOptions{MaxItems: maxServices}).Items {
if err != nil {
yield(DiscoveryEvent{}, err)
return
Expand Down
115 changes: 98 additions & 17 deletions directory/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,62 @@
# ODP directory package

Package `directory` searches the one canonical ODP directory for candidate Services. It does not
search Service catalogs. After discovery, an Agent inspects each result's live ODP document and
Package `directory` searches indexed Services and submitted Collections. It does not crawl
catalogs or index Offerings. After discovery, an Agent inspects each result's live ODP document and
queries that Service's Collections and Offerings.

The production origin is fixed at `https://api.inflowpay.ai`. Select `Sandbox` to use
the fixed `https://sandbox.inflowpay.ai` environment. Callers cannot configure another
origin.

## Search Services
## Search Services and Collections

```go
directoryClient, err := directory.New(directory.Options{})
if err != nil {
return err
}

search := directoryClient.Search(ctx, directory.DirectorySearchRequest{
SearchRequest: directory.SearchRequest{Query: "weather forecast", Limit: 25},
}, directory.IterationOptions{MaxItems: 25})

for result, err := range search.Items {
if err != nil {
return err
}
switch result.Type {
case "service":
fmt.Printf("Service: %s (%s)\n", result.Service.Name, result.Service.ServiceOrigin)
case "collection":
fmt.Printf("Collection: %s, ID %s, through %s\n",
result.Collection.Name, result.Collection.ID, result.Service.ServiceOrigin)
default:
fmt.Printf("Unsupported result type: %s\n", result.Type)
}
}
```

Omit `Types` to select both types, or provide `Types: []string{"collection"}` or `[]string{"service"}`.
The list must be nonempty and distinct. Filters apply to the owning Service for either type.

A Collection is identified by its owning Service origin and case-sensitive `Collection.ID`.
Inspect that Service, then call the Agent client's `GetCollection` with the ID.
`Result.IndexedAt` reports Collection freshness; `Result.Service.IndexedAt` reports its parent's
freshness. A Service may have `AvailableThrough` platform attribution. A Collection's attribution
is its owning `Service`.

Unknown types retain the wire type in `Type` and complete JSON in `Raw`; their `Service` and
`Collection` pointers are nil. Do not treat them as Services. Known types are validated and
retain additive metadata in `Additional`. Nested Service parsing omits unverified execution
metadata such as endpoint paths, just as Service-only search does.

The mixed endpoint returns at most 100 results (also its default limit), without continuation.
An absent `Next` does not promise that all matches were returned; refine the query or filters.
Collection search eligibility does not depend on permission to show a Directory landing card.
Mixed facets count all matching targets, not just returned items: a Service and two Collections
count as three. Descriptor facets use the owning Service's metadata.

## Search only Services

`SearchServices` lazily traverses directory pages and yields validated Service summaries. Filters
are structured and work without natural-language interpretation.
Expand All @@ -31,7 +79,7 @@ request := directory.SearchRequest{
Limit: 25,
}

for candidate, err := range directoryClient.SearchServices(ctx, request, directory.IterationOptions{}) {
for candidate, err := range directoryClient.SearchServices(ctx, request, directory.IterationOptions{}).Items {
if err != nil {
return err
}
Expand All @@ -44,14 +92,16 @@ InFlow or Solana through MPP. A protocol-only `PaymentFilter{Name: odp.ProtocolM
Service that advertises MPP. `Facets.Payments` reports protocol counts, `Facets.PaymentOptions`
reports each protocol-option count independently, and `Facets.Trust` reports trust protocol counts.

Use `SearchPages` when facet counts or page-level additive members are needed:
Every search returns a `SearchSequence` with independent lazy `Items` and `Responses` iterators.
Iterating both performs two searches. Creating a sequence performs no network requests and
captures the request values. Use `Responses` for facets, issues, additive fields or continuation:

```go
for page, err := range directoryClient.SearchPages(ctx, request, directory.IterationOptions{}) {
for page, err := range directoryClient.SearchServices(ctx, request, directory.IterationOptions{}).Responses {
if err != nil {
return err
}
consume(page.Items, page.Facets)
fmt.Printf("Results: %d, facets: %+v\n", len(page.Items), page.Facets)
}
```

Expand All @@ -60,34 +110,47 @@ that leaves that origin, repeats a page already visited, or cannot be resolved e
with an error. Stopping iteration stops network activity.

`IterationOptions.MaxPages` is the caller's page budget and defaults to 16. Reaching it ends the
sequence without an error, and the last page keeps the `Next` that `ContinueSearchPages` or
`ContinueSearchServices` resumes from:
sequence without an error, and the last response keeps `Next`. Resume mixed search using
`ContinueSearch`, or Service-only search using `ContinueSearchServices`:

```go
var resume string
for page, err := range directoryClient.SearchPages(ctx, request, directory.IterationOptions{MaxPages: 4}) {
for page, err := range directoryClient.SearchServices(ctx, request, directory.IterationOptions{MaxPages: 4}).Responses {
if err != nil {
return err
}
consume(page.Items, page.Facets)
fmt.Printf("Results: %d, facets: %+v\n", len(page.Items), page.Facets)
resume = page.Next
}
if resume != "" {
for page, err := range directoryClient.ContinueSearchPages(ctx, resume, directory.IterationOptions{}) {
// ...
for page, err := range directoryClient.ContinueSearchServices(ctx, resume, directory.IterationOptions{}).Responses {
if err != nil {
return err
}
fmt.Printf("Results: %d\n", len(page.Items))
}
}
```

A budget above 10,000 pages is rejected, and a directory that offers a continuation for 10,000
consecutive pages ends the traversal with an error rather than quietly appearing exhausted.
`MaxItems` bounds `SearchServices` at up to 10,000 results.
`MaxItems` bounds item iteration at up to 10,000 results; zero leaves it unbounded within the
request budget. Response iteration does not truncate responses to that item limit. Pass a
`context.Context` to cancel requests. Both search families share the same transport policies.

### Migrating existing Go callers

This is a breaking Go API change. Replace `SearchPages(...)` with
`SearchServices(...).Responses`, and `ContinueSearchPages(...)` with
`ContinueSearchServices(...).Responses`. Append `.Items` to existing `SearchServices(...)` and
`ContinueSearchServices(...)` iteration. Replace `SearchPage` with `SearchResponse[Service]`.
There are no deprecated aliases. The Agent's cross-Service Offering discovery remains Service-only.

## Malformed results

One unusable record does not discard the page it arrived on. A record that fails validation is
omitted from `Items` and reported in `SearchPage.Issues` with its index and the reason;
`IterationOptions.OnIssue` receives the same reports while iterating Services:
omitted from `Items` and reported in `SearchResponse.Issues` with its original index and the reason;
`IterationOptions.OnIssue` receives the same reports while iterating `Items`:

```go
options := directory.IterationOptions{OnIssue: func(issue directory.Issue) {
Expand All @@ -99,7 +162,25 @@ A malformed page envelope, by contrast, still fails the traversal.

## Suggestions

Suggestions help an Agent discover keyword vocabulary without downloading a global keyword list.
`Suggest` matches indexed names, descriptions and keywords, returning **names of matching
Services and Collections**, not the matching text itself. Despite `Prefix`, matching uses
substrings and whitespace-separated alternative terms. Names are deduplicated, with a maximum
and default of 25. These are candidate queries, not resource identifiers. Pass a selected string
to `Search`. Collection surfacing permission does not restrict suggestions.

```go
names, err := directoryClient.Suggest(ctx, directory.SuggestionRequest{
Prefix: "we", Limit: 10,
Filters: &directory.ServiceFilters{Keywords: []string{"weather"}},
})
```

`Suggest` sends POST `/v1/directory/suggestions`. Its optional `Filters` use the same
structure as search, including AEP, keywords, ODP operations, payments and trust.
Collection filters apply to the owning Service; the output remains names only.

`SuggestServices` uses GET and retains keyword-prefix suggestions for Service-only discovery.
It does not accept filters:

```go
suggestions, err := directoryClient.SuggestServices(ctx, directory.SuggestionRequest{
Expand Down
Loading
Loading