agent

package
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: Apache-2.0 Imports: 55 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// MaxLabelLength bounds a single label's length to keep the GIN
	// index entries small and prevent multi-KB tags from being smuggled
	// into the array.
	MaxLabelLength = 64

	// MaxLabelsPerOp caps the per-request add/remove list size.
	// Modeled on Gmail's 100/100 cap. Bigger than AgentMail's
	// (unspecified) but defensive enough that one PATCH can't try
	// to set thousands of labels.
	MaxLabelsPerOp = 50

	// LabelSystemPrefix marks server-applied system labels. User
	// writes that try to set a label starting with this prefix are
	// rejected with 400 — the namespace is reserved so future system
	// labels (auto-tagged, hitl-approved, …) don't collide with user
	// tags.
	LabelSystemPrefix = "e2a:"
)

Label validation constants. Public so tests can reference them.

View Source
const MaxAddressLen = 320

MaxAddressLen bounds a single email-address-bearing request string — a recipient item in to/cc/bcc, a reply_to override, or an agent email — as the FULL string (optional display name + <addr>), so a multi-megabyte display name can't ride in on an otherwise-valid address. 320 is the classic 64+1+255 addr-spec ceiling with the display-name form held to the same budget. Counted in Unicode code points (runes), NOT bytes, to match the OpenAPI maxLength semantics of the /v1 request schemas (JSON Schema counts code points; Huma validates with utf8.RuneCountInString). The /v1 schemas declare the same value declaratively; this runtime check is the shared backstop for every recipient list that reaches the send path.

Variables

View Source
var (
	ErrEventNotFound = errEventNotFound
	ErrEventExpired  = errEventExpired
)

Exported sentinels for the events lookup.

Functions

func AgentSuppressionAddedHook

func AgentSuppressionAddedHook(outbox webhookpub.Outbox) identity.AgentSuppressionTxHook

AgentSuppressionAddedHook returns the transactional event hook used by both authenticated management and public unsubscribe insertion paths.

func InsertReplayDelivery

func InsertReplayDelivery(ctx context.Context, pool *pgxpool.Pool, eventID, webhookID, eventType string, messageID *string, envelope []byte) (string, error)

InsertReplayDelivery wraps insertReplayDelivery, scheduling one redelivery.

func NewOutboundDeliverer

func NewOutboundDeliverer(sender *outbound.Sender) outboundsend.Deliverer

NewOutboundDeliverer builds the outboundsend.Deliverer adapter for main.go.

func NewOutboundRampGate

func NewOutboundRampGate(store *sendramp.Store, schedule sendramp.Schedule, enabled bool, clocks ...func() time.Time) outboundsend.RampGate

NewOutboundRampGate adapts the durable sendramp store to the worker-owned gate contract. The schedule is snapshotted by Store on the first eligible send; config changes therefore affect only domains that have not armed yet.

func NewOutboundSendStore

func NewOutboundSendStore(store *identity.Store, outbox webhookpub.Outbox, usageTracker usage.UsageTracker) outboundsend.Store

NewOutboundSendStore builds the outboundsend.Store adapter for main.go.

func NormalizeAndValidateLabelList

func NormalizeAndValidateLabelList(raw []string, op string) ([]string, error)

NormalizeAndValidateLabelList runs each entry through normalizeAndValidateLabel, dedups within the slice, and rejects if the slice is empty after trimming or exceeds the per-op cap. The `op` argument is used only to shape the error message.

func StripAgentSelfAliases

func StripAgentSelfAliases(addrs []string, agentEmail string) []string

StripAgentSelfAliases is the exported seam over stripAgentSelfAliases so the v1 httpapi reply/forward builders reuse the same self-alias stripping.

func ValidateDomain

func ValidateDomain(domain string) (string, error)

validateDomain runs the IDNA "Lookup" profile against a user-supplied domain string. Lookup is the strictest of the four IDNA profiles and is what DNS resolvers themselves apply when converting a name into a wire-format query — it rejects whitespace, control characters, invalid label combinations, and over-length names; converts Unicode IDN to Punycode along the way. We additionally require at least one period because IDNA accepts bare labels like "localhost" which aren't legal as a user-claimable domain.

Returns the ASCII-normalized form on success so callers can persist the canonical wire-format (e.g. "xn--e1afmkfd.xn--p1ai" for "пример.рф") instead of the raw input. ValidateDomain is the exported seam over validateDomain so the v1 httpapi layer reuses the exact IDN/punycode normalization + label-length checks instead of replicating the security-relevant parsing. Returns the normalized (Punycode) form.

func ValidateRecipients

func ValidateRecipients(groups ...[]string) error

validateRecipients ensures every entry in the joined To/CC/BCC slices is a syntactically valid RFC 5322 address. We use net/mail.ParseAddress rather than a custom regex because it handles bare local@domain, display-name forms ("Bob Smith <bob@x.com>"), quoted local parts, and IDN domains uniformly. Semantic validity (the mailbox actually exists, the user can receive) is checked downstream by the SMTP relay on a per-recipient basis — that's the right layer for best-effort delivery. The API layer's job is only to reject syntactic garbage that could never route through SMTP at all (no @, whitespace, etc.).

Returns the first invalid address found, with a wrapped parser error suitable for surfacing to the caller. Empty slices are not an error here — handlers already enforce "at least one recipient" separately with a more specific message. ValidateRecipients is the exported seam over validateRecipients so the v1 httpapi layer reuses the same RFC 5322 recipient check.

func ValidateWebhookURL

func ValidateWebhookURL(rawURL string) error

ValidateWebhookImageURL — see ValidateWebhookURL.

ValidateWebhookURL checks that a webhook URL is safe to call (SSRF protection).

Types

type API

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

func NewAPI

func NewAPI(store *identity.Store, sender *outbound.Sender, smtpRelay *outbound.SMTPRelay, userAuth *auth.UserAuth, usage usage.UsageTracker, smtpDomain, fromDomain, sharedDomain, publicURL string, production bool) *API

func (*API) ApproveInboundReviewCore

func (a *API) ApproveInboundReviewCore(ctx context.Context, userID string, msg *identity.ReviewMessageMeta) *OutboundError

ApproveInboundReviewCore releases a held INBOUND message to its agent's inbox (status pending_review → review_approved, now readable) and fires email.review_approved — the inbound analogue of ApprovePendingCore. There is no SES send and no draft edit: an inbound hold is a screening decision, not a draft.

msg is the dispatch view the account-scoped handler already resolved via GetReviewMessage (ownership + tenant isolation proven there); userID is the reviewing owner. The store transition is a compare-and-set on status='pending_review' AND agent_id, so a concurrent reviewer or the TTL sweep racing this call results in ErrNotPendingReview (409), never a double release.

func (*API) ApprovePendingCore

func (a *API) ApprovePendingCore(ctx context.Context, userID, messageID, expectedAgentEmail string, ovr ApproveOverrides, idemCompleteTx ApproveIdemCompleter) (*identity.Message, *OutboundError)

ApprovePendingCore is the HTTP-free core of the HITL approve→send: it verifies the held message (ownership-scoped + pending + optional expected-agent-email match + domain-verified), then runs ApproveAndSend with the shared send callback (self-send loopback / SES), records usage, and publishes the approved event. Both the legacy handler and the v1 layer call it. expectedAgentEmail (when non-empty) must equal the message's agent's email — mirrors the legacy verifyURLAgentEmail URL guard. On a nil-error return the local loopback or async enqueue has committed; the idempotency key must be Completed (cached), never Released. For queued delivery idemCompleteTx is invoked inside the approve-and-enqueue transaction.

func (*API) AuthenticatePrincipal

func (a *API) AuthenticatePrincipal(r *http.Request) (*identity.Principal, error)

AuthenticatePrincipal is the scope-aware seam (Slice 5a): same credential resolution as AuthenticateUser, but returns the full principal (user + scope + bound agent) so the v1 layer can enforce the hard scope ceiling. There is still exactly one place credentials are checked.

func (*API) AuthenticateUser

func (a *API) AuthenticateUser(r *http.Request) (*identity.User, error)

authenticateUser extracts and validates the bearer credential from the request, returning the owning user.

Dispatch is by token prefix:

  • ate2a_ → OAuth access token (fosite-validated via the configured provider). Rejected if missing, revoked, expired, or the provider isn't wired.
  • anything else (typically e2a_, but we accept legacy unprefixed keys too) → API key (api_keys table).

If no Authorization header is present, falls back to the session cookie used by the web dashboard. AuthenticateUser is the exported seam over authenticateUser so the v1 httpapi layer (internal/httpapi) reuses the exact same credential- resolution path (API key, OAuth bearer, session cookie) instead of forking a second one. There is one place credentials are checked.

func (*API) DeleteUserDataCore

func (a *API) DeleteUserDataCore(ctx context.Context, user *identity.User) (*identity.DeleteUserDataResult, error)

handleDeleteUserData wipes the authenticated user and every record tied to them, in a single Postgres transaction. Used for the right-of-deletion flow (GDPR Art. 17, CCPA "Do Not Sell or Share").

Requires `?confirm=DELETE` in the query string as a guardrail against accidental clicks. The HTTP method (DELETE) plus this confirmation matches the pattern other destructive APIs use (Stripe's account close, GitHub's repo delete).

DeleteUserDataCore counts OAuth rows for the audit line, best-effort notifies the external billing hook, then runs the cascading delete and merges the counts. HTTP-free; serves DELETE /v1/account?confirm=DELETE.

func (*API) DeliverOutbound

func (a *API) DeliverOutbound(ctx context.Context, user *identity.User, agent *identity.AgentIdentity, req outbound.SendRequest, msgType, replyToEmailMessageID string, referenced *identity.Message, idemCompleteTx AcceptIdemCompleter) (*OutboundResult, *OutboundError)

func (*API) DownloadLimitAllow

func (a *API) DownloadLimitAllow(key string) (bool, time.Duration, int, int, int)

DownloadLimitAllow exposes the per-IP attachment-download limiter (key = client ip). The download route is a raw capability-token endpoint outside the Huma rate-limit middleware, so it calls this directly. Returns the IETF snapshot.

func (*API) ExportUserDataCore

func (a *API) ExportUserDataCore(ctx context.Context, userID string) (*identity.UserExport, error)

handleExportUserData returns a complete dump of the authenticated user's data as JSON. Used for the right-of-access flow (GDPR Art. 15, CCPA equivalent). Output is deterministic-ordered (created_at) so a caller diffing exports across time gets stable results.

ExportUserDataCore assembles the full user-data export (store export + OAuth connections). HTTP-free; serves GET /v1/account/export.

func (*API) HoldForApprovalCore

func (a *API) HoldForApprovalCore(ctx context.Context, agent *identity.AgentIdentity, req outbound.SendRequest, msgType, replyToEmailMessageID string) (*identity.Message, error)

HoldForApprovalCore is the HTTP-free core of the HITL hold: it persists the pending outbound message, fires the async reviewer notification, and publishes the pending-approval event, returning the held message. Both the legacy handler and the v1 httpapi layer call it so there is exactly one hold-and-notify path (api-v1-redesign — outbound extraction).

func (*API) PollLimitAllow

func (a *API) PollLimitAllow(key string) (bool, time.Duration, int, int, int)

PollLimitAllow exposes the per-user read limiter (key = user id) and RegLimitAllow the per-IP registration limiter (key = client ip), each returning the IETF RateLimit snapshot so the v1 httpapi middleware can stamp RateLimit-Limit/Remaining/Reset. Both share the SAME buckets as the legacy handlers, so a caller is counted once across either surface.

func (*API) RegLimitAllow

func (a *API) RegLimitAllow(key string) (bool, time.Duration, int, int, int)

func (*API) RegisterRoutes

func (a *API) RegisterRoutes(r *mux.Router)

func (*API) RejectInboundReviewCore

func (a *API) RejectInboundReviewCore(ctx context.Context, userID, reason string, msg *identity.ReviewMessageMeta) *OutboundError

RejectInboundReviewCore drops a held INBOUND message (status pending_review → review_rejected; it stays hidden from the agent, raw payload retained for forensics) and fires email.review_rejected. Compare-and-set semantics as in ApproveInboundReviewCore.

func (*API) RejectPendingCore

func (a *API) RejectPendingCore(ctx context.Context, userID, messageID, expectedAgentEmail, reason string) (*identity.Message, *OutboundError)

RejectPendingCore is the HTTP-free core of HITL reject: optional expected-agent-email match (mirrors the legacy URL guard), then RejectPending + publish. Shared by the legacy handler and the v1 layer.

func (*API) SendLimitAllow

func (a *API) SendLimitAllow(key string) (bool, time.Duration)

SendLimitAllow exposes the per-agent outbound rate limiter so the v1 httpapi layer shares the *same* token bucket as the legacy handlers (a caller hitting the limit via either path is counted once). key = agent id.

func (*API) SendTestCore

func (a *API) SendTestCore(ctx context.Context, agent *identity.AgentIdentity) (*OutboundResult, *OutboundError)

SendTestCore accepts (or HITL-holds) a platform test email to the agent's own address. HTTP-free; shared by the legacy handler and the v1 layer. The caller has already authed, resolved + owned the agent, domain-verified, rate-limited, and run the message-send cap.

The message is PLATFORM-originated (From/envelope = noreply@<from_domain>) and traverses the real external SMTP → inbound route back to the agent's public address — that round-trip is the endpoint's purpose, so it must never route through the agent self-send loopback. Delivery is queue-first like every other send: a durable msg_ resource + outbound_send job are committed before any provider I/O (status=accepted), and the terminal outcome arrives via GET /v1/messages/{id} and the email.sent / email.failed events.

func (*API) SetAPIURL

func (a *API) SetAPIURL(u string)

SetAPIURL overrides the API/issuer base URL (default: publicURL). Set it when the programmatic API + MCP are served on a different host than the web app — that host becomes the OAuth issuer and the base for the token/ registration/revocation/jwks endpoints, while authorization_endpoint and the login/consent pages stay on publicURL. A no-op for the empty string so callers can pass an unset config value safely.

func (*API) SetApprovalSigner

func (a *API) SetApprovalSigner(s *approvaltoken.Signer)

SetApprovalSigner wires in the magic-link signer after construction so callers (and tests) that don't need HITL magic-link endpoints don't have to know about it. When nil, handleApproveMagicLink / handleRejectMagicLink respond with 404.

func (*API) SetBillingHookURL

func (a *API) SetBillingHookURL(s string)

SetBillingHookURL wires in the URL of an external billing service's user-event endpoint. When the user deletes their account, the API HMAC-signs a JSON payload and POSTs it there so the billing service can cancel the corresponding Stripe subscription. When empty (the self-host default), no hook fires — appropriate for deployments without a billing service. The same internal_api_secret is reused for the signature.

func (*API) SetDomainTeardownHook

func (a *API) SetDomainTeardownHook(h func(ctx context.Context, tx pgx.Tx, domain string) error)

SetDomainTeardownHook wires the per-domain SES deprovision enqueue run in the account-delete transaction (decision 4 / Slice 4). Optional.

func (*API) SetEnforcer

func (a *API) SetEnforcer(e limits.Enforcer)

SetEnforcer wires in the resource-limits enforcer. When nil (the default) every check passes — handlers behave as if every user has unlimited capacity. The cmd/e2a runtime always sets it; tests that don't care about limits omit it and continue to work as before.

func (*API) SetIdempotencyStore

func (a *API) SetIdempotencyStore(s *idempotency.Store)

SetIdempotencyStore enables Idempotency-Key processing on the outbound /send and /reply endpoints. When nil (the default) the header is silently ignored — keeps the agent package usable in environments that don't have postgres wired or want to disable the feature. The cmd/e2a runtime always sets it.

func (*API) SetInternalAPISecret

func (a *API) SetInternalAPISecret(s string)

SetInternalAPISecret wires in the shared HMAC secret used to authenticate the /api/internal/limits/invalidate endpoint. When empty (default), that endpoint returns 503 — self-host operators who don't run a billing provisioner never need to configure it.

func (*API) SetManagedUnsubscribeIssuer

func (a *API) SetManagedUnsubscribeIssuer(i ManagedUnsubscribeIssuer)

func (*API) SetMetrics

func (a *API) SetMetrics(m telemetry.Metrics)

SetMetrics wires the slice 10 observability backend. Default is telemetry.NoOp; production passes telemetry.NewLog() or a real counter backend.

func (*API) SetNotifyEnqueuer

func (a *API) SetNotifyEnqueuer(e NotifyEnqueuer)

SetNotifyEnqueuer wires the HITL-notification accept-tx enqueuer. When nil, holdForApproval still persists the pending message but enqueues no notification — useful for tests that don't want the SMTP traffic, and for deployments without a configured relay / public URL.

func (*API) SetOAuthProvider

func (a *API) SetOAuthProvider(p fosite.OAuth2Provider)

SetOAuthProvider wires in the fosite-backed OAuth provider. When nil, /oauth2/* endpoints return 404 (matches the SetApprovalSigner / SetNotifyEnqueuer pattern of "optional capability, silently absent when not wired"). Operators who don't want OAuth enabled simply don't call this.

func (*API) SetOAuthStorage

func (a *API) SetOAuthStorage(s *oauth.Storage)

SetOAuthStorage wires in the OAuth storage handle separately from the provider. The consent handler needs Pool() to begin a pgx tx that spans the agent-create (identity pkg) and the auth-code insert (fosite → oauth pkg). Provider-only callers (e.g. /token) don't need it, but it's required for /consent to work; setting one without the other is a misconfiguration the consent handler surfaces as a 503.

func (*API) SetOIDCAuth

func (a *API) SetOIDCAuth(oa *auth.OIDCAuth)

SetOIDCAuth wires optional OIDC browser login. NewOIDCAuth returns nil when disabled, so callers may invoke this unconditionally and leave the routes genuinely absent from deployments that do not opt in.

func (*API) SetOutboundEnqueuer

func (a *API) SetOutboundEnqueuer(e OutboundEnqueuer)

SetOutboundEnqueuer wires the mandatory queue-first outbound pipeline. Tests may leave it unset to verify that a miswired process fails closed before provider I/O.

func (*API) SetOutbox

func (a *API) SetOutbox(o webhookpub.Outbox)

SetOutbox wires the slice-4 transactional outbox. Used by the outbound /send handler for the email.sent event (post-side-effect: PublishBestEffortTx). Other trigger sites (HITL) continue to use the legacy publisher; they will migrate in a future slice if/when their handlers gain transactional plumbing.

func (*API) SetPollRateLimit

func (a *API) SetPollRateLimit(perMinute int)

SetPollRateLimit sets the per-user poll budget to perMinute requests per minute (config: rate_limits.poll_per_minute). Values <= 0 are ignored and keep the built-in default.

func (*API) SetPoolForEvents

func (a *API) SetPoolForEvents(p *pgxpool.Pool)

SetPoolForEvents wires the raw pgxpool.Pool used by the events handlers. Kept separate from the store so a future refactor can route through a higher-level abstraction without changing the handler signatures.

func (*API) SetSigner

func (a *API) SetSigner(s *agentauth.Signer)

SetSigner wires the RS256 agent-token signer (Slice 5b). Optional — when nil or disabled, /.well-known/jwks.json serves an empty key set and the agent-identity token paths (later sub-slices) report "not enabled".

func (*API) SetSubscriberStore

func (a *API) SetSubscriberStore(s *webhook.SubscriberStore)

SetSubscriberStore wires the subscriber-store dependency after NewAPI. Same optional-setter convention as SetEnforcer / etc.

func (*API) SetUsageStore

func (a *API) SetUsageStore(s *usage.Store)

SetUsageStore wires in the usage store used by handleGetMyLimits to surface the user's current counts (agents, domains, messages this month, storage bytes) alongside the resolved caps. Separate from the usage.UsageTracker (which is for recording events) so the dashboard read path can stay alive even when usage-tracking is otherwise off.

func (*API) SetWebSocketHub

func (a *API) SetWebSocketHub(h WebSocketHub)

func (*API) UnsubscribeLimitAllow

func (a *API) UnsubscribeLimitAllow(key string) (bool, time.Duration, int, int, int)

UnsubscribeLimitAllow exposes the distinct per-IP managed-unsubscribe limiter. Mailbox providers centralize link scanning and one-click requests, so this has a provider-friendly budget and never consumes attachment quota.

func (*API) WWWAuthenticateChallenge

func (a *API) WWWAuthenticateChallenge(r *http.Request) string

WWWAuthenticateChallenge is the v1-surface seam (internal/httpapi) for the RFC 6750 challenge. The legacy mux had the auth error in hand at the 401 site (writeAuthError); the v1 layer rejects via the canonical envelope and reaches this from a response wrapper that only knows the status was 401, so we re-run the same credential resolution to recover the typed error and classify the challenge identically (expired vs invalid). 401s are rare, so the extra resolution is cheap, and routing it through the one auth path keeps the surfaces from diverging.

type AcceptIdemCompleter

type AcceptIdemCompleter func(ctx context.Context, tx pgx.Tx, result *OutboundResult) error

AcceptIdemCompleter completes the idempotency key inside the delivery tx with the exact result the wire will render. External sends cache 202/accepted; terminal local loopback caches 200/sent. nil when the request carries no Idempotency-Key (or no idempotency store is wired).

type ApproveIdemCompleter

type ApproveIdemCompleter func(ctx context.Context, tx pgx.Tx, approved *identity.Message) error

ApproveIdemCompleter completes an approval's idempotency key inside the async approve-and-enqueue transaction. It receives the resolved message so the httpapi layer can cache the exact SendResultView (including edited, method, and sent_as) that the live request returns.

type ApproveOverrides

type ApproveOverrides = approveRequest

ApproveOverrides are the optional reviewer edits applied on approve (exported alias of the internal body type so the v1 httpapi layer can build them).

type DNSRecordCheck

type DNSRecordCheck struct {
	TXTFound bool
	MX       string
	SPF      string
	DKIM     string
}

checkDomainRecords runs the three per-record probes plus the TXT ownership check. In dev mode, all checks short-circuit to "found" / true so domain verification flows can be exercised without real DNS.

Probe semantics:

  • TXT: any TXT record contains the verification token (ownership proof)
  • MX: any MX record points at smtpDomain (mail routing)
  • SPF: any apex TXT record begins with v=spf1 (case-insensitive) and contains smtpDomain as a substring (case-insensitive). Advisory only — SPF does not gate domain verification.
  • DKIM: when dkimSelector + dkimPublicKey are present, looks up "{selector}._domainkey.{domain}" and matches the stored public key — "found" on a match, "mismatch" when a key is published but doesn't match (see classifyDKIM), "missing" when none is present. Domains without a stored keypair report "deferred" — these are pre-migration rows that the next claim would key.

DNSRecordCheck is the exported diagnostic from CheckDomainRecords.

func CheckDomainRecords

func CheckDomainRecords(domain, smtpDomain, verificationToken, dkimSelector, dkimPublicKey string, production bool) DNSRecordCheck

CheckDomainRecords is the exported seam over checkDomainRecords so the v1 httpapi layer reuses the exact DNS-probe logic for domain verification.

type DeliveryStatusJSON

type DeliveryStatusJSON = deliveryStatusJSON

DeliveryStatusJSON is the exported per-event delivery roll-up.

type EventView

type EventView = eventView

EventView is the exported event wire shape (alias of the internal type, so the JSON contract is identical).

func GetEventForUser

func GetEventForUser(ctx context.Context, pool *pgxpool.Pool, userID, eventID string) (*EventView, error)

GetEventForUser wraps the internal getEvent query.

func ListEventsForUser

func ListEventsForUser(ctx context.Context, pool *pgxpool.Pool, userID, eventType, agentID, conversationID, messageID string, since, until *time.Time, cursorCreatedAt time.Time, cursorID string, limit int) ([]EventView, error)

ListEventsForUser wraps the internal listEvents query.

type ForwardRequest

type ForwardRequest struct {
	To             []string              `json:"to"`
	CC             []string              `json:"cc,omitempty"`
	BCC            []string              `json:"bcc,omitempty"`
	Body           string                `json:"text,omitempty"`
	HTMLBody       string                `json:"html,omitempty"`
	ConversationID string                `json:"conversation_id,omitempty"`
	Attachments    []outbound.Attachment `json:"attachments,omitempty"`
}

ForwardRequest is the JSON body for /v1/agents/{email}/messages/{id}/forward.

type LimitsCaps

type LimitsCaps struct {
	MaxAgents        int   `json:"max_agents"`
	MaxDomains       int   `json:"max_domains"`
	MaxMessagesMonth int   `json:"max_messages_month"`
	MaxStorageBytes  int64 `json:"max_storage_bytes"`

} // @name LimitsCaps

type LimitsInfo

type LimitsInfo struct {
	PlanCode   string      `json:"plan_code"`
	Limits     LimitsCaps  `json:"limits"`
	Usage      LimitsUsage `json:"usage"`
	UpgradeURL string      `json:"upgrade_url"`

} // @name LimitsInfo

LimitsInfo is the response body for GET /v1/account. It bundles the user's resolved caps with their current usage so the dashboard renders the "you're using X of Y" surface in one round trip. plan_code and upgrade_url come straight from the limits row (opaque to OSS — written by whatever provisions the row); when no row exists, plan_code is the operator default and upgrade_url is empty.

type LimitsUsage

type LimitsUsage struct {
	Agents        int   `json:"agents"`
	Domains       int   `json:"domains"`
	MessagesMonth int   `json:"messages_month"`
	StorageBytes  int64 `json:"storage_bytes"`

} // @name LimitsUsage

type ManagedUnsubscribeIssuer

type ManagedUnsubscribeIssuer interface {
	Ready() error
	Issue(ctx context.Context, userID, agentID, recipient string) (string, error)
}

ManagedUnsubscribeIssuer is deliberately narrow: it receives only the final canonical recipient and returns a stored, absolute public capability URL.

type NotifyEnqueuer

type NotifyEnqueuer interface {
	EnqueueNotifyTx(ctx context.Context, tx pgx.Tx, messageID string) (int64, error)
}

NotifyEnqueuer inserts the HITL approval-notification job (QueueNotify) in the hold accept-tx, so the reviewer's email is enqueued atomically with the pending_review row (docs/design/hitl-notify-river.md). Satisfied by *hitlnotify.Jobs. When nil (notifier unconfigured), HoldForApprovalCore persists the hold on the plain path and sends no notification.

type OAuthClientPublicMetadata

type OAuthClientPublicMetadata struct {
	ClientID         string   `json:"client_id"`
	ClientName       string   `json:"client_name"`
	RedirectURIs     []string `json:"redirect_uris"`
	Scopes           []string `json:"scopes"`
	ClientIDIssuedAt int64    `json:"client_id_issued_at"`
	// AccountEligible reports whether the consent screen may offer account
	// (workspace-admin) scope for this client: true iff "account" is on the
	// client's registered scope ceiling (which DCR writes only when the
	// redirect is account-eligible AND the client requested it). Drives the
	// consent UI (the Account option is disabled otherwise); fosite re-checks
	// granted ⊆ registered at grant time, so this is UX, not the security
	// boundary.
	AccountEligible bool `json:"account_eligible"`
}

OAuthClientPublicMetadata is the subset of an oauth_clients row we surface to anonymous readers. The consent page (web/) fetches this so it can render the friendly client_name beside the requested scope. No secrets are present in this struct; secret_hash and internal bookkeeping fields are deliberately omitted.

type OAuthError

type OAuthError struct {
	Error            string `json:"error"`
	ErrorDescription string `json:"error_description,omitempty"`
}

OAuthError is the RFC 7591 §3.2.2 / RFC 6749 §5.2 JSON error body. Used for DCR-side errors and direct (non-redirected) authorize errors.

type OAuthMetadata

type OAuthMetadata struct {
	Issuer                                 string   `json:"issuer"`
	AuthorizationEndpoint                  string   `json:"authorization_endpoint"`
	TokenEndpoint                          string   `json:"token_endpoint"`
	RegistrationEndpoint                   string   `json:"registration_endpoint"`
	RevocationEndpoint                     string   `json:"revocation_endpoint"`
	ResponseTypesSupported                 []string `json:"response_types_supported"`
	ResponseModesSupported                 []string `json:"response_modes_supported"`
	GrantTypesSupported                    []string `json:"grant_types_supported"`
	CodeChallengeMethodsSupported          []string `json:"code_challenge_methods_supported"`
	TokenEndpointAuthMethodsSupported      []string `json:"token_endpoint_auth_methods_supported"`
	RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported"`
	ScopesSupported                        []string `json:"scopes_supported"`
	// JWKSURI points at the public key set agents verify e2a-minted JWTs
	// against (Slice 5b). Always present once a signer surface exists; the
	// endpoint serves {"keys":[]} when no signing key is configured.
	JWKSURI string `json:"jwks_uri,omitempty"`
	// AgentAuth is the auth.md agent-identity discovery block (Slice 5b-2).
	// Present only when the agent-identity surface is enabled (a signing key is
	// configured) — advertising endpoints that 501 would mislead clients.
	AgentAuth *agentAuthMetadata `json:"agent_auth,omitempty"`
	// RFC 9207 §3 — advertises that authorize responses carry `iss`
	// (we emit it manually in writeAuthorizeRedirect since fosite
	// v0.49 doesn't ship native RFC 9207 support).
	AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
}

OAuthMetadata is the RFC 8414 authorization-server metadata document served at /.well-known/oauth-authorization-server. Field names use snake_case per §2 of the RFC. Only the fields e2a actually advertises are present — omitting an OPTIONAL field (introspection_endpoint, jwks_uri, …) signals to clients that the feature isn't supported.

type OAuthRegisterRequest

type OAuthRegisterRequest struct {
	ClientName              string   `json:"client_name"`
	RedirectURIs            []string `json:"redirect_uris"`
	GrantTypes              []string `json:"grant_types,omitempty"`
	ResponseTypes           []string `json:"response_types,omitempty"`
	TokenEndpointAuthMethod string   `json:"token_endpoint_auth_method,omitempty"`
	Scope                   string   `json:"scope,omitempty"`
}

OAuthRegisterRequest is the RFC 7591 §2 client metadata POSTed to /oauth2/register. Unknown fields are tolerated (forward-compat with RFC 7591 extensions) but ignored.

type OAuthRegisterResponse

type OAuthRegisterResponse struct {
	ClientID                string   `json:"client_id"`
	ClientIDIssuedAt        int64    `json:"client_id_issued_at"`
	ClientName              string   `json:"client_name"`
	RedirectURIs            []string `json:"redirect_uris"`
	GrantTypes              []string `json:"grant_types"`
	ResponseTypes           []string `json:"response_types"`
	TokenEndpointAuthMethod string   `json:"token_endpoint_auth_method"`
	Scope                   string   `json:"scope"`
}

OAuthRegisterResponse is the RFC 7591 §3.2.1 success envelope. Echoes the metadata the server stored (after defaults applied) plus the assigned client_id and issuance timestamp. No client_secret — public clients only.

type OutboundEnqueuer

type OutboundEnqueuer interface {
	EnqueueSendTx(ctx context.Context, tx pgx.Tx, messageID string) (int64, error)
}

OutboundEnqueuer is the accept-tx's handle on the shared River client — it inserts the outbound_send job in the same transaction as the message row. *outboundsend.Jobs satisfies it. Main always injects it; a nil value fails closed before provider I/O.

type OutboundError

type OutboundError struct {
	Status int
	Code   string
	Msg    string
	// Details carries optional code-specific structured context across the
	// agent/httpapi boundary. It stays transport-neutral to avoid a package cycle.
	Details map[string]any
}

OutboundError carries an HTTP status + message so both the legacy handler (http.Error) and the v1 httpapi layer (error envelope) render it consistently. Code is the machine-branchable code the v1 layer uses.

func (*OutboundError) Error

func (e *OutboundError) Error() string

type OutboundResult

type OutboundResult struct {
	Held              bool
	PendingMessageID  string
	ApprovalExpiresAt *time.Time
	MessageID         string // the e2a msg_ id (GET-able) for every send method
	ProviderMessageID string // provider/SES id when sent via smtp
	SentAs            string // "own_address" | "relay" (decision 4)
	Method            string // "smtp" | "loopback"
	// Status is the send progression the wire maps to `status`. Empty on the
	// synchronous path (the caller renders "sent"); "accepted" on the async path
	// (durably persisted + queued; the terminal outcome arrives via email.sent /
	// email.failed). See async-message-pipeline.md, slice C.
	Status string
}

OutboundResult is the HTTP-free outcome of DeliverOutbound.

type ReplayEvent

type ReplayEvent struct {
	EventType         string
	MessageID         *string
	Envelope          []byte
	MatchedWebhookIDs []string
}

ReplayEvent is the exported snapshot used to schedule a redelivery.

func LoadReplayEvent

func LoadReplayEvent(ctx context.Context, pool *pgxpool.Pool, userID, eventID string) (*ReplayEvent, error)

LoadReplayEvent wraps loadEventForReplay (ownership-scoped). Returns ErrEventNotFound / ErrEventExpired for the respective cases.

type WebSocketHub

type WebSocketHub interface {
	IsConnected(agentID string) bool
	Send(agentID string, msg []byte) bool
}

WebSocketHub is the narrow live-delivery seam shared with internal/ws.

Jump to

Keyboard shortcuts

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