Documentation
¶
Overview ¶
Package http is Harbor's HTTP transport driver for the unified tool catalog (Phase 27). It ships two registration paths — inline `RegisterHTTPTool(...)` for the dev loop and a UTCP-style YAML manifest loader for operator deployments — converging on the same `tools.ToolDescriptor` shape (Transport = TransportHTTP). Every invocation runs through the Phase 26 `ToolPolicy` reliability shell (D-024); the driver itself does NOT loop independently.
Static auth (API key, bearer, cookie) is supported via the `AuthSpec` value plus a secret loaded from operator-supplied config; secrets MUST NOT live in URL templates or request payloads (AGENTS.md §7). OAuth / token-exchange flows are deferred to Phase 30 via the unified pause/resume primitive.
Concurrent reuse (D-025): every HTTP `ToolDescriptor` is safe under N concurrent invocations against the same underlying `*http.Client` because (a) per-invocation state lives on the goroutine stack (request value, response value, attempt context, classifier), and (b) the descriptor's compiled `text/template`s + cached schema validators are read-only after construction.
Index ¶
- Variables
- func IsRateLimited(err error) (time.Duration, bool)
- func RegisterHTTPTool(cat tools.ToolCatalog, name, method, urlTemplate string, opts ...HTTPOption) error
- func RegisterManifest(cat tools.ToolCatalog, m *Manifest) error
- type AuthKind
- type AuthSpec
- type HTTPOption
- func WithArgsSchema(schema []byte) HTTPOption
- func WithAuth(spec AuthSpec, secret string) HTTPOption
- func WithAuthScopes(scopes ...string) HTTPOption
- func WithBodyTemplate(tmpl string) HTTPOption
- func WithClient(c *http.Client) HTTPOption
- func WithDescription(s string) HTTPOption
- func WithHTTPSource(id tools.ToolSourceID) HTTPOption
- func WithHeaders(h map[string]string) HTTPOption
- func WithLoading(m tools.LoadingMode) HTTPOption
- func WithOutSchema(schema []byte) HTTPOption
- func WithPolicy(p tools.ToolPolicy) HTTPOption
- func WithSideEffect(s tools.SideEffect) HTTPOption
- func WithTags(tags ...string) HTTPOption
- type Manifest
- type ManifestAuth
- type ManifestTool
- type RateLimitedError
Constants ¶
This section is empty.
Variables ¶
var ( // ErrAuthMissing — auth spec declares a secret-bearing kind but // the supplied secret is empty. Surfaced loudly at register-time // so misconfiguration is caught at boot, not at first invocation. ErrAuthMissing = errors.New("http: auth secret missing or empty") // ErrAuthInvalidSpec — AuthSpec internally inconsistent (e.g. // api_key with both HeaderName and QueryParam set). ErrAuthInvalidSpec = errors.New("http: auth spec invalid") )
Sentinel auth errors.
var ( // ErrTemplateRender — text/template execution failed (e.g. // missing variable referenced via {{ .Args.Foo }}, or a tag // the template engine couldn't parse). Wraps the underlying // error via %w. ErrTemplateRender = errors.New("http: URL/body template render failure") // ErrTemplateSecretLeak — a manifest / inline template attempted // to reference the .Auth namespace (the credential boundary). // Surfaced at registration time so the misconfiguration never // reaches a live invocation. AGENTS.md §7 — no credential // passthrough by default. ErrTemplateSecretLeak = errors.New("http: template attempted to interpolate auth/secret reference") // ErrUnsupportedMethod — RegisterHTTPTool got a method outside // the HTTP allowlist (POST / GET / PUT / DELETE / PATCH). ErrUnsupportedMethod = errors.New("http: HTTP method not supported") // ErrIdentityMissing — the per-invocation ctx had no identity // quadruple. Identity is mandatory (AGENTS.md §6). ErrIdentityMissing = errors.New("http: identity missing from ctx") // ErrHTTPStatus — non-2xx response classifier emitted this so // upstream callers can errors.Is against it; the wrapped detail // carries status + retry hint. ErrHTTPStatus = errors.New("http: response status") )
Sentinel errors for the inline / manifest registration paths and per-invocation failures. Callers compare via errors.Is.
var ErrManifestInvalid = errors.New("http: manifest invalid")
ErrManifestInvalid wraps every manifest-loader / validation failure. Callers compare via errors.Is.
Functions ¶
func IsRateLimited ¶
IsRateLimited reports whether err is (or wraps) a RateLimitedError. Returns the delay alongside the boolean for ergonomic callers.
func RegisterHTTPTool ¶
func RegisterHTTPTool( cat tools.ToolCatalog, name, method, urlTemplate string, opts ...HTTPOption, ) error
RegisterHTTPTool registers a single HTTP tool inline. Returns the catalog's Register error (typically ErrToolDuplicateName) or any pre-registration validation error (unsupported method, secret leak in template, invalid auth spec).
Identity-mandatory: the registered descriptor reads the (tenant, user, session) triple from ctx on every Invoke and fails with ErrIdentityMissing when absent. Phase 30 will extend this with tool-side OAuth tokens (per-identity); Phase 27 honours only the operator-configured static auth secret.
Concurrent reuse (D-025): the produced descriptor is safe for N concurrent goroutines — per-invocation state (request, response, classifier output) lives on the stack; cached templates and compiled schema validators are read-only after construction.
func RegisterManifest ¶
func RegisterManifest(cat tools.ToolCatalog, m *Manifest) error
RegisterManifest registers every tool in m against cat. Errors short-circuit on the first failure with the offending tool name in the wrapped detail; previously-registered tools remain in the catalog (the caller controls catalog lifetime).
Concurrent reuse (D-025): each registered descriptor is safe under N concurrent invocations (see RegisterHTTPTool).
Types ¶
type AuthKind ¶
type AuthKind string
AuthKind is the static-auth discriminator. Phase 27 ships three values; OAuth and token-exchange land in Phase 30.
const ( // AuthKindNone — no auth applied (default). Useful for public // endpoints (e.g. weather APIs that take an API key as a query // parameter via AuthKindAPIKey + QueryParam, or completely open // services). AuthKindNone AuthKind = "" // AuthKindAPIKey — secret placed in either a header (when // HeaderName is set) or a query parameter (when QueryParam is // set). Exactly one MUST be set. AuthKindAPIKey AuthKind = "api_key" // AuthKindBearer — RFC 6750 bearer token. Sets the // "Authorization: Bearer <secret>" header. AuthKindBearer AuthKind = "bearer" // AuthKindCookie — secret placed in the named cookie. AuthKindCookie AuthKind = "cookie" )
type AuthSpec ¶
AuthSpec configures static authentication for an HTTP tool. The shape is value-typed (no pointers) so a tool descriptor that declares auth at construction has it baked-in for every invocation; the secret value lives separately in `httpToolConfig.secret` so the AuthSpec itself stays loggable / printable.
The Kind drives which of the optional fields is consulted:
- AuthKindAPIKey: HeaderName XOR QueryParam (exactly one).
- AuthKindBearer: no extra fields.
- AuthKindCookie: CookieName (mandatory).
type HTTPOption ¶
type HTTPOption func(*httpToolConfig)
HTTPOption configures an HTTP tool at registration. Mirrors the option-functional pattern from the inproc driver.
func WithArgsSchema ¶
func WithArgsSchema(schema []byte) HTTPOption
WithArgsSchema attaches the input JSON schema (raw bytes). When absent, the descriptor's Validate is a permissive pass-through — the operator opts INTO validation explicitly.
func WithAuth ¶
func WithAuth(spec AuthSpec, secret string) HTTPOption
WithAuth attaches a static auth spec and the corresponding secret value. The secret is loaded from operator-supplied config (env variable, manifest auth_ref); it MUST NOT come from a request payload (D-NNN: no credential passthrough by default).
func WithAuthScopes ¶
func WithAuthScopes(scopes ...string) HTTPOption
WithAuthScopes adds required catalog-visibility scopes.
func WithBodyTemplate ¶
func WithBodyTemplate(tmpl string) HTTPOption
WithBodyTemplate sets a text/template for the request body. The rendered output is sent with `Content-Type: application/json` unless overridden by WithHeaders.
func WithClient ¶
func WithClient(c *http.Client) HTTPOption
WithClient overrides the *http.Client used for this tool. Operators with bespoke transports (custom TLS roots, proxy, retryable client) wire them in here.
func WithDescription ¶
func WithDescription(s string) HTTPOption
WithDescription overrides the Tool's planner-facing description.
func WithHTTPSource ¶
func WithHTTPSource(id tools.ToolSourceID) HTTPOption
WithHTTPSource overrides the descriptor's ToolSourceID. Defaults to "inline:<tool-name>" for the inline registration path or "manifest:<filename>" for the manifest path.
func WithHeaders ¶
func WithHeaders(h map[string]string) HTTPOption
WithHeaders adds static headers applied AFTER auth so an operator can't accidentally overwrite the auth header with a static one.
func WithLoading ¶
func WithLoading(m tools.LoadingMode) HTTPOption
WithLoading overrides LoadingMode (default: LoadingAlways).
func WithOutSchema ¶
func WithOutSchema(schema []byte) HTTPOption
WithOutSchema attaches the output JSON schema (raw bytes).
func WithPolicy ¶
func WithPolicy(p tools.ToolPolicy) HTTPOption
WithPolicy overrides the per-tool ToolPolicy.
func WithSideEffect ¶
func WithSideEffect(s tools.SideEffect) HTTPOption
WithSideEffect declares the tool's side-effect class.
type Manifest ¶
type Manifest struct {
// Tools declares the HTTP tool surface.
Tools []ManifestTool `yaml:"tools"`
// Auth declares static-auth specs keyed by `auth_ref`. A tool
// references one via ManifestTool.AuthRef.
Auth map[string]ManifestAuth `yaml:"auth,omitempty"`
}
Manifest is the UTCP-style YAML schema describing one or more HTTP tools as Harbor `Tool`s. `LoadManifest` parses a manifest file and `RegisterManifest` registers its tools onto a catalog.
The boot-path wiring is NOT yet built: no production path reads `ToolsConfig.HTTPManifests` and calls these (the config validator rejects a populated list for exactly that reason — see docs/notes/sdk-friction-audit.md §1). Until the loader lands, embedders call `LoadManifest` + `RegisterManifest` (or the inline `RegisterHTTPTool`) from their own assembly code.
Schema (YAML):
auth:
weather_key:
kind: api_key
header: X-API-Key
secret: ${WEATHER_API_KEY} # env-ref form is mandatory; literal secrets rejected
github_bot:
kind: bearer
secret: ${GITHUB_BOT_TOKEN}
tools:
- name: weather.lookup
method: GET
url_template: https://api.weather.example/v1/now?city={{ .Args.city | urlquery }}
description: Look up current weather by city name.
auth_ref: weather_key
args_schema: '{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}'
out_schema: '{"type":"object","properties":{"temp_c":{"type":"number"}}}'
side_effect: external
tags: [weather]
loading: always
- name: gh.create_issue
method: POST
url_template: https://api.github.com/repos/{{ .Args.owner }}/{{ .Args.repo }}/issues
body_template: '{"title":{{ .Args.title }},"body":{{ .Args.body }}}'
auth_ref: github_bot
Security boundary: a template that references the `.Auth` namespace is rejected loudly at load time (AGENTS.md §7 — no credential passthrough). Secrets are sourced from `auth_ref` lookups only, and the manifest may NOT inline literal secret strings — every `auth.<name>.secret` value MUST match `${ENV_VAR}` shape so the actual value lives in the operator's environment / secret manager.
func LoadManifest ¶
LoadManifest reads a YAML manifest from disk and returns a fully-validated `*Manifest` with `${ENV_VAR}` secret references expanded against `os.Getenv`. Returns `ErrManifestInvalid` wrapped with the offending detail on any validation failure (unknown fields, missing referenced env var, literal secret value, name collision, template-secret leak, etc.).
Path traversal is mitigated via `filepath.Clean` — the resulting absolute path is what the loader reads.
type ManifestAuth ¶
type ManifestAuth struct {
Kind AuthKind `yaml:"kind"`
Header string `yaml:"header,omitempty"`
Query string `yaml:"query,omitempty"`
Cookie string `yaml:"cookie,omitempty"`
Secret string `yaml:"secret" secret:"true"`
}
ManifestAuth is one static-auth spec entry in a manifest. The Secret field carries the operator-supplied secret value in `${ENV_VAR}` reference form; literal values are rejected at load time (AGENTS.md §7).
type ManifestTool ¶
type ManifestTool struct {
Name string `yaml:"name"`
Method string `yaml:"method"`
URLTemplate string `yaml:"url_template"`
Description string `yaml:"description,omitempty"`
ArgsSchema string `yaml:"args_schema,omitempty"`
OutSchema string `yaml:"out_schema,omitempty"`
Headers map[string]string `yaml:"headers,omitempty"`
Body string `yaml:"body_template,omitempty"`
AuthRef string `yaml:"auth_ref,omitempty"`
SideEffect tools.SideEffect `yaml:"side_effect,omitempty"`
Tags []string `yaml:"tags,omitempty"`
AuthScopes []string `yaml:"auth_scopes,omitempty"`
Loading tools.LoadingMode `yaml:"loading,omitempty"`
Policy *tools.ToolPolicy `yaml:"policy,omitempty"`
// SourceID overrides the descriptor's ToolSourceID; defaults to
// "manifest:<file>#<name>" when empty.
SourceID string `yaml:"source_id,omitempty"`
}
ManifestTool is one HTTP-tool entry in a manifest.