directory

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 26, 2026 License: MIT Imports: 19 Imported by: 0

README

ODP directory package

Package directory searches indexed Services and Collections from ODP and OpenAPI sources. It does not crawl catalogs or index Offerings. Each mixed result identifies its exact discovery document. The Agent package navigates ODP catalogs only; it does not execute OpenAPI operations.

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 and Collections

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), source: %s\n", result.Service.Name,
			result.Service.Source.Type, result.Service.Source.URL)
	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 result's Service.ServiceID identifies the indexed Service. Several source documents can share an API origin, so the origin alone does not identify an imported Service. Service.Source.URL preserves the exact document path and query; it can be hosted on a different origin from the API.

Check Service.Source.Type before choosing the next operation:

  • SourceODP: inspect the Service's live ODP document. For a Collection, call the Agent client's GetCollection with the case-sensitive Collection.ID.
  • SourceOpenAPI: read Service.Source.URL with an OpenAPI-aware client. A Collection ID identifies a Directory presentation group, not an ODP Collection endpoint. Its source is the parent's document.
  • Any other value: display the metadata or report an unsupported source. Do not assume ODP.

Source.X402Discovery means that a supporting fixed-path x402 discovery document was detected. It does not prove that an operation accepts payment or that the caller can execute it. Imported description, language and localizations may be absent; their Go values are empty strings or nil slices. Imported results do not populate ODP Operations. Native ODP results retain their required metadata validation. SearchServices returns native ODP Services only.

Mixed search requires the Directory's source-aware response format. A missing or empty source URL is reported as a record issue; the client does not infer a document URL from the API origin.

Result.IndexedAt reports Collection freshness; Result.Service.IndexedAt reports its parent's freshness. A Service may have Publisher attribution with PublisherID, Name, and WebsiteURL. The website is a display link, not a discovery or execution target. Additional response members are retained; publisher attribution may be absent or null. 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.

Filter mixed search or suggestions by source using the same ServiceFilters:

filters := &directory.ServiceFilters{
	Sources: []directory.SourceType{directory.SourceOpenAPI},
}
search := directoryClient.Search(ctx, directory.DirectorySearchRequest{
	SearchRequest: directory.SearchRequest{Query: "weather", Filters: filters},
}, directory.IterationOptions{})
names, err := directoryClient.Suggest(ctx, directory.SuggestionRequest{
	Prefix: "weather", Filters: filters,
})

Omit Sources for all sources. A supplied list must contain one or two distinct values from SourceODP and SourceOpenAPI. Values within the list are alternatives; other filter categories are combined with it. Collection filters use the owning Service's source. Source filters do not make native SearchServices return OpenAPI entries.

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.

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

request := directory.SearchRequest{
	Query: "compute",
	Filters: &directory.ServiceFilters{
		Keywords: []string{"gpu", "accelerator"},
		Payments: []directory.PaymentFilter{{
			Name: odp.ProtocolMPP,
			Options: []odp.PaymentOption{odp.PaymentOptionInflow, odp.PaymentOptionSolana},
		}},
	},
	Limit: 25,
}

for candidate, err := range directoryClient.SearchServices(ctx, request, directory.IterationOptions{}).Items {
	if err != nil {
		return err
	}
	fmt.Printf("%s: %s\n", candidate.Name, candidate.ServiceOrigin)
}

Options within one payment filter are alternatives. The example matches Services that accept either InFlow or Solana through MPP. A protocol-only PaymentFilter{Name: odp.ProtocolMPP} matches any Service that advertises MPP. Facets.Payments reports protocol counts, Facets.PaymentOptions reports each protocol-option count independently, and Facets.Trust reports trust protocol counts.

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:

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

Opaque continuation links are followed with GET on the selected canonical origin. A continuation that leaves that origin, repeats a page already visited, or cannot be resolved ends the traversal 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 response keeps Next. Resume mixed search using ContinueSearch, or Service-only search using ContinueSearchServices:

var resume string
for page, err := range directoryClient.SearchServices(ctx, request, directory.IterationOptions{MaxPages: 4}).Responses {
	if err != nil {
		return err
	}
	fmt.Printf("Results: %d, facets: %+v\n", len(page.Items), page.Facets)
	resume = page.Next
}
if resume != "" {
	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 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 SearchResponse.Issues with its original index and the reason; IterationOptions.OnIssue receives the same reports while iterating Items:

options := directory.IterationOptions{OnIssue: func(issue directory.Issue) {
	log.Printf("directory %s result %d: %s", issue.Scope, issue.Index, issue.Message)
}}

A malformed page envelope, by contrast, still fails the traversal.

Suggestions

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.

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, sources 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:

suggestions, err := directoryClient.SuggestServices(ctx, directory.SuggestionRequest{
	Prefix: "gp",
	Limit:  5,
})

Sandbox

directoryClient, err := directory.New(directory.Options{
	Environment: directory.Sandbox,
})

Options.HTTPClient permits transport policy and test injection while preserving the selected canonical origin. The client accepts up to five same-origin redirects, bounds response bodies, and returns non-success responses as *directory.RequestError with the status and response headers. RequestError.Retryable marks the 429 and 5xx statuses worth retrying, and Message quotes only a structured field of a JSON error document, stripped of control characters and clamped in length.

Directory results filter unrecognized enrollment, payment, and trust descriptors while preserving recognized, validated protocol capabilities.

Documentation

Overview

Package directory searches indexed Services and Collections in the canonical Directory.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

func New

func New(options Options) (*Client, error)

func (*Client) ContinueSearch added in v0.5.0

func (client *Client) ContinueSearch(ctx context.Context, next string, options IterationOptions) SearchSequence[Result]

func (*Client) ContinueSearchServices added in v0.4.0

func (client *Client) ContinueSearchServices(ctx context.Context, next string, options IterationOptions) SearchSequence[Service]

func (*Client) Environment

func (client *Client) Environment() Environment

func (*Client) Search added in v0.5.0

func (client *Client) Search(ctx context.Context, request DirectorySearchRequest, options IterationOptions) SearchSequence[Result]

func (*Client) SearchServices

func (client *Client) SearchServices(ctx context.Context, request SearchRequest, options IterationOptions) SearchSequence[Service]

func (*Client) Suggest added in v0.5.0

func (client *Client) Suggest(ctx context.Context, request SuggestionRequest) ([]string, error)

func (*Client) SuggestServices

func (client *Client) SuggestServices(ctx context.Context, request SuggestionRequest) ([]string, error)

type CollectionSummary added in v0.5.0

type CollectionSummary struct {
	Additional  odp.AdditionalMembers
	ID          string
	Name        string
	Description string
}

type DirectorySearchRequest added in v0.5.0

type DirectorySearchRequest struct {
	SearchRequest
	Types []string `json:"types,omitempty"`
}

type Environment

type Environment string
const (
	Production Environment = "production"
	Sandbox    Environment = "sandbox"
)

type Facet

type Facet[Value any] struct {
	Count int64
	Value Value
}

type Facets

type Facets struct {
	Enrollment     []Facet[odp.EnrollmentProtocol]
	Keywords       []Facet[string]
	Operations     []Facet[odp.OperationDescriptor]
	PaymentOptions []Facet[PaymentOptionFacetValue]
	Payments       []Facet[odp.PaymentProtocol]
	Trust          []Facet[odp.TrustProtocol]
}

type IndexedService added in v0.5.0

type IndexedService struct {
	Service
	ServiceID string
	Source    Source
}

IndexedService carries Directory metadata. Check Source.Type before using an ODP Agent client; imported Services may omit description, language, localizations and operations.

type Issue added in v0.4.0

type Issue struct {
	Index   int
	Message string
	Scope   IssueScope
}

Issue describes one Directory record this client discarded, and why.

type IssueScope added in v0.4.0

type IssueScope string
const IssueResult IssueScope = "result"
const IssueService IssueScope = "service"

type IterationOptions

type IterationOptions struct {
	MaxItems int
	MaxPages int
	// OnIssue, when set, is called for each record a page carried that could not be used. Without
	// it an item traversal cannot tell a page of unusable records from a page of no matches.
	OnIssue func(Issue)
}

type OperationFilter added in v0.2.0

type OperationFilter struct {
	Authentication odp.AuthenticationRequirement `json:"authentication,omitempty"`
	Name           odp.Operation                 `json:"name"`
}

type Options

type Options struct {
	Environment Environment
	HTTPClient  *http.Client
}

type PaymentFilter added in v0.2.0

type PaymentFilter struct {
	Authentication odp.AuthenticationRequirement `json:"authentication,omitempty"`
	Name           odp.Protocol                  `json:"name"`
	Options        []odp.PaymentOption           `json:"options,omitempty"`
}

type PaymentOptionFacetValue added in v0.3.2

type PaymentOptionFacetValue struct {
	Name   odp.Protocol      `json:"name"`
	Option odp.PaymentOption `json:"option"`
}

type Publisher added in v0.6.0

type Publisher struct {
	Additional  odp.AdditionalMembers
	PublisherID string
	Name        string
	WebsiteURL  string
}

type RequestError

type RequestError struct {
	Header    http.Header
	Message   string
	Retryable bool
	Status    int
}

func (*RequestError) Error

func (err *RequestError) Error() string

type Result added in v0.5.0

type Result struct {
	Additional odp.AdditionalMembers
	Type       string
	Service    *IndexedService
	Collection *CollectionSummary
	Publisher  *Publisher
	IndexedAt  time.Time
	Raw        json.RawMessage
}

Result retains unknown resource types in Raw without interpreting them as Services.

type SearchRequest

type SearchRequest struct {
	Filters *ServiceFilters `json:"filters,omitempty"`
	Limit   int             `json:"limit,omitempty"`
	Query   string          `json:"query,omitempty"`
}

type SearchResponse added in v0.5.0

type SearchResponse[Item any] struct {
	Additional odp.AdditionalMembers
	Facets     *Facets
	// Issues reports records this page carried that could not be used. They are reported rather
	// than raised because a directory is not an ODP protocol role: one unusable record says
	// nothing about the rest of the page.
	Issues []Issue
	Items  []Item
	Next   string
}

type SearchSequence added in v0.5.0

type SearchSequence[Item any] struct {
	Items     iter.Seq2[Item, error]
	Responses iter.Seq2[SearchResponse[Item], error]
}

SearchSequence exposes independent, lazy traversals of the same search.

type Service

type Service struct {
	Additional       odp.AdditionalMembers
	Description      string
	DocumentationURL string
	IndexedAt        time.Time
	Keywords         []string
	Language         string
	Localizations    []string
	Name             string
	Operations       []odp.OperationDescriptor
	Protocols        *odp.ServiceProtocols
	ServiceOrigin    string
	StatusURL        string
	SupportURL       string
	WebsiteURL       string
}

type ServiceFilters

type ServiceFilters struct {
	Enrollment []odp.EnrollmentProtocol `json:"enrollment,omitempty"`
	Keywords   []string                 `json:"keywords,omitempty"`
	Operations []OperationFilter        `json:"operations,omitempty"`
	Payments   []PaymentFilter          `json:"payments,omitempty"`
	Sources    []SourceType             `json:"sources,omitempty"`
	Trust      []odp.TrustProtocol      `json:"trust,omitempty"`
}

type Source added in v0.5.1

type Source struct {
	Additional    odp.AdditionalMembers
	Type          SourceType
	URL           string
	X402Discovery bool
}

Source identifies the discovery document, not an endpoint to invoke.

type SourceType added in v0.5.1

type SourceType string
const (
	SourceODP     SourceType = "odp"
	SourceOpenAPI SourceType = "openapi"
)

type SuggestionRequest

type SuggestionRequest struct {
	Filters *ServiceFilters `json:"filters,omitempty"`
	Limit   int             `json:"limit,omitempty"`
	Prefix  string          `json:"prefix"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL