agui

package
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Jun 20, 2026 License: MIT Imports: 41 Imported by: 0

Documentation

Overview

auth.go is the WEB-03 in-binary web-auth boundary (D-01..D-04): a constant-time operator-secret login that mints an HMAC-signed session cookie, a RequireAuth middleware that gates the whole origin except the public paths (login + assets + GET /healthz), a POST /logout that clears the cookie, and a capability gate on the only mutating route (POST /agent/run). The cookie crypto lives in auth_cookie.go; this file owns the HTTP surface + the consumer-side identity seam.

The boundary is stdlib-only (crypto/hmac + crypto/sha256 + crypto/subtle + net/http + time) — no gorilla/* or other 3rd-party session library (RESEARCH §Alternatives Considered). It reads ONLY the session cookie, never a client-supplied auth/identity header (T-24-13: trusting X-Forwarded-User / Authorization from the client is the golang-security client-header anti-pattern; the trust-proxy posture means "Aura stays hands-off", not "Aura reads a proxy-injected identity header").

CSRF posture (T-24-19, RESEARCH §Discretion Resolution): SameSite=Strict is the only CSRF control this phase. For a same-origin SPA with no cross-origin write path that covers the classic vector; a double-submit token is NOT added in Phase 24. Re-evaluate if Phase 28/29 introduces a cross-origin write surface.

auth_cookie.go holds the stdlib-only session-cookie crypto for the WEB-03 in-binary web-auth boundary (D-01/D-02). It is split out of auth.go to keep each file well under the 600-LOC ceiling (CLAUDE.md NO GOD CLASS): this file owns the secret compare + the HMAC sign/verify + the cookie (un)set helpers; auth.go owns the HTTP middleware + handlers.

The session is a STATELESS, HMAC-SHA256-signed cookie — no session store, no new credential storage (D-01). The cookie value is base64url(payload)"."base64url(MAC) where payload is "{identityID}|{issuedUnix}"; a single server key derived once from sha256(AURA_WEB_AUTH_SECRET) (RESEARCH A2 — one operator secret governs both login and signing) keys the MAC. Tamper ⇒ MAC mismatch ⇒ reject; expiry ⇒ issued_at+TTL check ⇒ reject. Every compare is constant-time (subtle.ConstantTimeCompare for the secret, hmac.Equal for the MAC) so the boundary leaks no timing oracle.

gorilla/securecookie + gorilla/sessions are deliberately NOT used: they are not vendored and adding them would violate the no-framework posture (server.go:88) for what is ~40 LOC of stdlib HMAC (RESEARCH §Alternatives Considered).

Package agui is the AG-UI protocol transport adapter (Slice 8a). It consumes Aura's in-process iter.Seq2[*agent.Event, error] stream and maps it onto the official AG-UI community Go SDK event surface. The boundary is one-way: agui imports agent, the agent runtime NEVER imports agui (D-17, CI-enforced via scripts/agui_boundary_check.sh). The translator (translator.go) is a pure function — no I/O, no goroutines — so it is property- and golden-testable in isolation.

Index

Constants

View Source
const ArtifactEventName = "aura.artifact"

ArtifactEventName is the stable AG-UI CUSTOM-event name the artifact branch emits (D-06). A channel consumer keys on this name to render a delivered file (Telegram → sendDocument, CLI → print path, AG-UI HTTP → pass-through). It is namespaced so it never collides with another application's custom events. Exported so the channel consumers (e.g. telegram artifact.go, plan 13-06) key on the SAME canonical name rather than a duplicated magic string.

View Source
const DisplayEventName = "aura.display"

DisplayEventName is the stable AG-UI CUSTOM-event name the typed-display branch emits (Phase 26, D-PROTO). It is the additive twin of ArtifactEventName: the cockpit sseAdapter keys on this name to attach the DisplayPayload to the matching tool part by tool_call_id. Namespaced so it never collides with another application's custom events.

Variables

View Source
var ErrEmptyThreadID = errors.New("agui: threadId must not be empty")

ErrEmptyThreadID is the Aura-semantic validation sentinel ValidateRunInput returns. It is distinct from the SDK's parse errors: the SDK's RunAgentInput.UnmarshalJSON owns camel/snake JSON shape, while this guards Aura's one hard run precondition — a thread to resolve.

Functions

func RequireAuth added in v1.0.0

func RequireAuth(next http.Handler, deps AuthDeps) http.Handler

RequireAuth gates the WHOLE origin (D-03) except the public paths. When auth is not configured (SecretConfigured==false, loopback dev) it returns `next` unchanged — a no-op pass-through (mirrors server.go withCORS), safe because the Plan-01 boot guard only permits an unconfigured secret on a loopback bind. When active it:

  1. lets isPublicPath requests + GET /healthz through (login + assets + liveness);
  2. reads ONLY r.Cookie(sessionCookieName) — never a client auth/identity header (T-24-13, golang-security client-header anti-pattern);
  3. redirects a browser GET to the login page (302) / 401s an API request on a missing or invalid cookie;
  4. verifySession (HMAC + absolute TTL);
  5. confirms the bound identity still exists (a deleted identity invalidates the session);
  6. stashes the principal on the request context and calls next.

func RequireCapability added in v1.0.0

func RequireCapability(next http.Handler, deps AuthDeps, capability string) http.Handler

RequireCapability wraps a mutating-route handler with the capability_grants check (D-04): it reads the principal RequireAuth stashed, asks the identity store HasCapability(principal, capability), and 403s on a missing principal, a store error, or a denied capability. This is the seam that exercises the dormant capability_grants scaffolding on the ONLY mutating route (POST /agent/run); the seeded `local` identity passes via its `*` wildcard. It invents NO governance write routes — those land in Phase 28. Exported so the composition root (cmd/aura) interposes it on the parent mux.

func SanitizeString

func SanitizeString(msg string) string

SanitizeString strips credential-bearing substrings from an arbitrary string: whole DB DSNs collapse to a scheme marker, generic URL userinfo collapses to `scheme://[redacted]@`, and bearer/api-key/token shapes collapse to a `prefix[redacted]` marker (WR-03). The DSN pass runs first so the generic userinfo pass never reaches the DB schemes.

func Subscribe

func Subscribe(ctx context.Context, threadID, runID string, idgen IDGenerator, turn iter.Seq2[*agent.Event, error]) <-chan Event

Subscribe composes Plan 01's Translate with a single-subscriber Fanout and returns a receive-only channel of AG-UI events for ONE in-process consumer of an agent turn. Given the threadID/runID, an IDGenerator, and the agent's iter.Seq2[*agent.Event, error] turn stream, it translates the turn and pumps it through the cap-64, drop-on-full, ctx-cancel-clean Fanout — the same backpressure + leak discipline a future Telegram channel consumes (amendment #35 agui_subscriber.go seam). The channel closes when the turn ends or ctx is cancelled.

This seam is transport-free: no HTTP, no SSE (that is server.go in a later plan). For N concurrent consumers, build a *Fanout directly and Subscribe() per consumer; this helper is the single-consumer convenience over that same pump.

Reasoning forward-compat: the translated stream may later carry REASONING_* events (the dual-field reasoning/reasoning_content data-plane, amendment #57). This phase only surfaces the alias — no reasoning parsing is added here.

func Translate

func Translate(threadID, runID string, idgen IDGenerator, seq iter.Seq2[*agent.Event, error], showReasoning bool) iter.Seq2[events.Event, error]

Translate maps Aura's per-token *agent.Event stream onto a valid AG-UI events.Event sequence. It is a PURE function (no I/O, no goroutines): RUN_STARTED first, RUN_FINISHED(success) last, with a chunk-coalescing text-message state machine in between. idgen owns id minting so every emitted messageId/toolCallId is non-empty (both are Validate()-required by the SDK).

Run-boundary policy (locked OQ1 a): the real (*LlmAgent).Run streams per-token chunk Events AND a final Event carrying the full answer + FinishReason. We stream the deltas (one TEXT_MESSAGE_CONTENT per non-empty delta) and treat the final Event as END-only — its Content is NOT re-emitted as a CONTENT (no double-stream); its usage StateDelta becomes a STATE_DELTA event.

showReasoning controls the REASONING_MESSAGE_CONTENT payload: false (the default privacy posture) substitutes redactedReasoningDelta; true passes the real CoT delta through so an opted-in consumer (Telegram's live 💭 window) can display it. The REASONING_* lifecycle envelope is emitted either way — only the delta text differs.

func ValidateRunInput

func ValidateRunInput(in types.RunAgentInput) error

ValidateRunInput checks the Aura-semantic preconditions for a run: a non-empty threadId (resolved to a conversation downstream by conversations.Get, which owns the UUID-shape check). It deliberately does NOT require a message: an empty Messages list is the continue-after-resume signal (D-05 / CR-01) — handleRun resolves userMsg=nil (lastUserMessage) and drives Runner.Turn(…, nil) over the rehydrated history (the resolved ask_user answer is already a RoleTool turn the cockpit re-drive POSTs against after an inline approval resolves). It does NOT re-implement JSON parsing (the SDK's UnmarshalJSON does) nor re-validate the UUID shape (conversations.Get's parseUUID does).

Types

type ApprovalStore added in v1.0.0

type ApprovalStore interface {
	ListPendingAll(ctx context.Context, limit int) ([]askuser.Pending, error)
}

ApprovalStore is the narrow cross-thread HITL-read surface the approval center consumes (APRV-01 / D-04; *askuser.Store satisfies it implicitly). ListPendingAll aggregates every still-pending pause across ALL conversations in total order (priority DESC, created_at ASC, token ASC). Declared consumer-side so the server depends only on the one method the approvals read calls — the three-action resolve goes through the Runner (SubmitAnswers), not this store. A Server with no ApprovalStore wired answers the approvals routes 503 (the read is optional wiring; the daemon composition root sets it via SetApprovalStore).

type AssetService added in v1.0.0

AssetService is the narrow asset API surface consumed by AG-UI handlers.

type AuthDeps added in v1.0.0

type AuthDeps struct {
	// Secret is the raw operator passphrase (AURA_WEB_AUTH_SECRET) the login form is
	// compared against constant-time. Empty ⇒ login fail-closed.
	Secret string
	// SigningKey is the derived HMAC-SHA256 key (sha256(Secret)) that signs/verifies
	// the session cookie.
	SigningKey []byte
	// TTL is the absolute session lifetime; a zero value falls back to defaultSessionTTL.
	TTL time.Duration
	// SecretConfigured reports whether in-binary auth is active. When false the whole
	// gate is bypassed (loopback dev) — safe because GuardWebBind only permits an
	// unconfigured secret on a loopback bind.
	SecretConfigured bool
	// LocalIdentityID is the seeded `local` identity a successful login binds the
	// session to (the capability_grants principal, D-02/CORE-03).
	LocalIdentityID string
	// Identities is the capability/identity store seam (a *identity.Store at runtime).
	Identities identityChecker
	// LoginPath is the public login page route an unauthenticated browser GET is
	// redirected to (default "/login").
	LoginPath string
	// AuthBasePath, when non-empty, is the credential-flow route prefix served by the
	// embedded Authula provider (its BasePath, "/auth"). Every path under it is public
	// (reachable WITHOUT a session) because login / TOTP-verify happen before a session
	// exists; Authula's own per-route metadata + CSRF middleware gate those routes. Empty
	// (the passphrase path) means no such prefix is public.
	AuthBasePath string
	// PublicAsset reports whether a path is a real embedded static asset the login page
	// needs before a session exists (the shared SPA bundle/styles, the PWA service
	// worker/manifest, icons). It is wired from webui.IsPublicAsset at the composition
	// root (cmd/aura/serve_webui.go); a nil predicate means no static asset is public
	// (the gate still serves the login route + GET /healthz).
	PublicAsset func(path string) bool
	// PublicRoute reports whether a non-asset route is intentionally reachable before
	// a session. The composition root uses this for tiny bootstrap contracts such as
	// the auth-provider config; credential subtrees should still use AuthBasePath.
	PublicRoute func(r *http.Request) bool
	// SessionValidator, when non-nil, REPLACES the HMAC cookie-validation core inside
	// RequireAuth (the Option-A2 seam, AURA_WEB_AUTH_PROVIDER=authula): given a request
	// it returns the bound Aura identity UUID + ok. When nil (the default,
	// AURA_WEB_AUTH_PROVIDER=passphrase) RequireAuth keeps the stdlib HMAC verifySession
	// path verbatim. Either way the result feeds the SAME existence re-check +
	// withPrincipal + RequireCapability contract — only the issuer/validator of the
	// cookie changes, never the principalKey{} downstream.
	SessionValidator func(r *http.Request) (identityID string, ok bool)
}

AuthDeps carries everything the login handler + RequireAuth + the capability gate need, threaded from bootServe. SecretConfigured is the master switch: when false (loopback dev, which the Plan-01 boot guard confines to loopback) RequireAuth is a no-op pass-through and login is unavailable. SigningKey is sha256(AURA_WEB_AUTH_SECRET).

func (AuthDeps) LoginHandler added in v1.0.0

func (d AuthDeps) LoginHandler() http.HandlerFunc

LoginHandler returns the POST /login handler: it reads the form passphrase (body size-bounded like server.go's handleRun), constant-time compares it against the configured secret (validateSecret — fail-closed on an empty/unconfigured secret), and on success mints a session cookie bound to the local identity and 303-redirects to "/". On any failure it returns a GENERIC 401 with no Set-Cookie and no oracle distinguishing "wrong password" from "no secret configured" (T-24-15, golang-security V7). The passphrase is never echoed.

func (AuthDeps) LogoutHandler added in v1.0.0

func (d AuthDeps) LogoutHandler() http.HandlerFunc

LogoutHandler returns the POST /logout handler: it clears the session cookie (MaxAge < 0) and 303-redirects to the login page. It is safe to call without a session (idempotent) and never reads the cookie value.

type CapabilitySource added in v1.0.1

type CapabilitySource interface {
	ListCapabilities(ctx context.Context, identityID string) ([]string, error)
}

CapabilitySource is the D-06 capability-picker source: the creating operator's own grants (the handler filters out '*' for the checklist and re-validates the subset before the saga). identity.Store satisfies it.

type ConversationStore

type ConversationStore interface {
	Get(ctx context.Context, conversationID string) (conversations.Conversation, error)
	LoadHistory(ctx context.Context, conversationID string) ([]llm.Message, error)
	List(ctx context.Context, includeArchived bool) ([]conversations.Conversation, error)
	SearchConversationTurns(ctx context.Context, query string, limit int) ([]conversations.SearchResult, error)
	UpdateStatus(ctx context.Context, conversationID, status string) error
	Rename(ctx context.Context, conversationID, title string) error
	SetTitleIfNull(ctx context.Context, conversationID, title string) error
	Delete(ctx context.Context, conversationID string) error
	ListContextRotEvents(ctx context.Context, conversationID string) ([]conversations.RotEvent, error)
	// D-09 / CHAT-05 branch surface (plan 25-07). ListBranches enumerates the navigable
	// branch leaves; ForkBranch writes a new sibling branch (edit-a-user-turn /
	// regenerate) chained off the diverging turn's parent and returns the new leaf seq.
	// CanonicalBranchLeaf is the default selection (the conversation's canonical tip).
	// All three are on the concrete *conversations.Store; declared here so agui depends
	// only on the methods it calls.
	ListBranches(ctx context.Context, conversationID string) ([]conversations.Branch, error)
	ForkBranch(ctx context.Context, conversationID string, divergeSeq int, role, content string) (int, uuid.UUID, error)
	CanonicalBranchLeaf(ctx context.Context, conversationID string) (int, error)
}

ConversationStore is the narrow conversation surface the AG-UI server consumes (D-A2-02, "accept interfaces, return structs"). *conversations.Store satisfies it implicitly. Get resolves a threadId → conversation (404 chokepoint); LoadHistory rehydrates the byte-identical turn history for the GET messages projection. The CHAT-02 conversation-management surface (Phase 25) widens this with List/SearchConversationTurns/UpdateStatus/Rename/Delete/SetTitleIfNull and a read-only ListContextRotEvents (the D-11 microcompact ladder gauge marker) — all already on the concrete *conversations.Store, declared here consumer-side so agui depends only on the methods it calls, never the whole Store.

type Event

type Event = events.Event

Event is the AG-UI event surface re-exported under the agui package so in-process consumers (e.g. the Phase-13 Telegram channel) reference agui.Event, not the external events.Event — keeping github.com/ag-ui-protocol/... out of every call site (PRD Slice 8: "Type aliases per evitare leak del package esterno nei call sites"). Only the subscriber surface is aliased; the SDK is not blanket re-exported.

type EventType

type EventType = events.EventType

EventType is the aliased AG-UI event discriminator (agui.Event.Type() returns it), re-exported alongside Event so a consumer can switch on event kind without importing the SDK.

type Fanout

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

Fanout distributes a single translated AG-UI event stream to N concurrent in-process subscribers. It wraps the Translate output (iter.Seq2[events.Event, error]) and, for each event, sends to every subscriber via a cap-64 buffered, drop-on-full, never-block pump (the openai_compat Stream analog plus a default-drop arm). The single producer goroutine started by Run is the SOLE sender on every subscriber channel and closes them all on exit (ctx-cancel OR source end), so the stream is goleak-clean (Pitfall 4).

Fanout is NOT on the HTTP path (RESEARCH diagram line 134): server.go does not route through it. It is the in-process seam the Phase-13 Telegram channel consumes.

Errors from the wrapped source are already mapped to a RUN_ERROR event by Translate (the translated stream is pure events.Event), so Fanout never inspects the error slot — it just forwards whatever Translate yields.

func NewFanout

func NewFanout(source iter.Seq2[events.Event, error]) *Fanout

NewFanout wraps a translated event stream. Subscribe before Run to register a consumer; Run then drives the source once and fans out to all registered subscribers.

func (*Fanout) Run

func (f *Fanout) Run(ctx context.Context)

Run starts the single producer goroutine that ranges the wrapped source once and fans each event to every subscriber. It returns immediately; the goroutine exits on ctx-cancel or source end, closing every subscriber channel on the way out (sole sender closes — golang-concurrency principle #4). Run is non-blocking and must be called AT MOST ONCE: a second Run would launch a second producer that double-closes the subscriber channels (panic: close of closed channel). The started guard makes the misuse loud at the call site instead (WR-06).

func (*Fanout) Subscribe

func (f *Fanout) Subscribe() <-chan events.Event

Subscribe registers a new consumer and returns its receive-only channel. The channel is cap-64 buffered and is closed by the producer goroutine when the source ends or ctx is cancelled. Subscribe MUST be called before Run: a subscriber registered after the producer has snapshotted f.subs is never fed (and racing the snapshot is a data race), so Subscribe-after-Run panics loudly rather than failing silently (WR-06).

type GovernanceProviders added in v1.0.1

type GovernanceProviders struct {
	MCP       MCPBoardProvider
	Skills    SkillsBoardProvider
	Scheduler SchedulerBoardProvider
}

GovernanceProviders bundles the three read-only board providers so the composition root wires them in one call. Any field may be nil; the corresponding board's handler (Plan 02) answers 503 when its provider is unset.

type GraphView added in v1.0.0

type GraphView interface {
	Schema(ctx context.Context) (knowledge.GraphSchema, error)
	Query(ctx context.Context, in knowledge.GraphIntent) (knowledge.GraphResult, error)
}

GraphView is the narrow read-only graph surface the handlers consume (D-A2-02: declared consumer-side so the handler depends only on the two methods it calls, not the whole *knowledge.GraphView). *knowledge.GraphView satisfies it. Schema returns the live label/rel-type/property-key overview (D-06); Query dispatches a structured intent through the plan-01 compile → assertReadOnly → Read → normalize path. A Server with no GraphView wired answers both routes 503 (the wiring is optional; the daemon composition root sets it via SetGraphView).

type IDGenerator

type IDGenerator interface {
	NewMessageID() string
	NewReasoningID() string
	NewToolResultID(toolCallID string) string
}

IDGenerator owns AG-UI id minting so the translator can guarantee non-empty messageId/toolCallId (both are Validate()-required by the SDK) and so tests can inject deterministic ids for stable golden compares. NewMessageID mints an assistant message run id; NewReasoningID mints the SEPARATE rsn- message id a REASONING lifecycle carries (distinct from the assistant TEXT_MESSAGE id so a consumer never conflates CoT with the answer); NewToolResultID mints the message id carrying a tool result (correlated to its toolCallID).

func NewIDGenerator

func NewIDGenerator() IDGenerator

NewIDGenerator returns the default uuid-v4-backed IDGenerator.

type Identity added in v1.0.0

type Identity struct {
	ID   string
	Name string
	Kind string
}

Identity is the minimal projection the auth boundary observes — it mirrors the public field set of identity.Identity so *identity.Store satisfies identityChecker structurally without agui importing the concrete return type's full surface.

type ImageFetcher added in v1.0.0

type ImageFetcher interface {
	FetchImage(ctx context.Context, convID, rawURL string) ([]byte, string, error)
}

ImageFetcher is the narrow SSRF-safe image-fetch surface the proxy consumes (*web.Client satisfies it via FetchImage). Declared consumer-side (D-A2-02) so the agui server depends only on the one method it calls, not the whole web.Client — and so a unit test can pass a scripted fake. The returned (bytes, mediaType) is already gated by the web SSRF guard + image content-type allowlist + size cap; an error is a sanitized *web.WebError (no IP/host leak).

type MCPBoardProvider added in v1.0.1

type MCPBoardProvider interface {
	Servers() mcp.ManagedConfig
	Probe(ctx context.Context, name string, server mcp.ManagedServer) mcp.ProbeResult
}

MCPBoardProvider is the read-only MCP-governance surface (GOV-01): the static row snapshot from the managed config plus a bounded, per-row live probe. ProbeServer is the structured live probe extracted in this plan; the handler bounds the supplied ctx with a per-request timeout so a hung server fails only its own row.

type OnboardingService added in v1.0.1

type OnboardingService interface {
	CapabilitySource
}

OnboardingService is the server-held provisioning + interview surface the onboarding wizard handlers (Plan 05) consume: start a session, apply one step intent to the server-held session, run the cross-store provisioning saga, and poll the Telegram link. The concrete service (session store + saga) is built in Plan 05; this plan only declares the seam so the field + setter exist. The capability source is embedded so the picker read goes through the same narrow interface.

type ReadinessProbe

type ReadinessProbe struct {
	Name  string
	Check func(context.Context) error
}

ReadinessProbe is one named dependency-readiness check the server runs on /readyz. Check returns nil when the dependency is reachable, a non-nil error (surfaced sanitized in the 503 body) when it is not. Probes are injected by the composition root (cmd/aura wires the real PG + Neo4j probes) so they stay testable and the set is easy to extend without touching the handler.

type Runner

type Runner interface {
	Turn(ctx context.Context, convID string, userMsg *string) iter.Seq2[*agent.Event, error]
	SubmitAnswers(ctx context.Context, answers map[string]runner.ResponseInput) (int, error)
	NewConversation(ctx context.Context) (string, error)
	// TurnBranch is the D-09 / CHAT-05 re-run-from-a-point: it drives a fresh agent
	// round over the SELECTED branch path (leafSeq) with no fresh user message — the
	// branch's turns were already persisted by ForkBranch. *runner.Runner satisfies it.
	TurnBranch(ctx context.Context, convID string, leafSeq int) iter.Seq2[*agent.Event, error]
}

Runner is the narrow agent-driver surface the server consumes (D-A2-02; *runner.Runner satisfies it implicitly). Turn drives one round over a thread and yields the agent Event stream; SubmitAnswers resolves protocol-native HITL resume entries (resolved→accept, cancelled→cancel) before the Turn. Declared consumer-side so the server depends only on the two methods it calls, not the whole Runner — and so unit tests pass scripted fakes.

type SchedulerBoardProvider added in v1.0.1

type SchedulerBoardProvider interface {
	ListActiveTasks(ctx context.Context) ([]cron.Task, error)
	ListRunsForTask(ctx context.Context, taskID string, limit, offset int) ([]cron.Run, error)
}

SchedulerBoardProvider is the read-only scheduler-governance surface (GOV-03): the active task list and a paginated, newest-first run history per task.

type Server

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

Server is the minimal AG-UI HTTP gateway (Slice 8b): POST /agent/run streams a translated agent turn as SSE, GET /threads/{id}/messages returns the persisted history as a MESSAGES_SNAPSHOT JSON body. It is the thinnest glue over EXISTING seams — the narrow Runner + ConversationStore, the translator, and the SDK SSE writer. The bind is hardcoded loopback by the daemon (auth deferred this phase, amendment #35); the loopback bind IS the compensating control (T-12-08).

func NewServer

func NewServer(run Runner, conv ConversationStore, cfg ServerConfig) *Server

NewServer builds the gateway over the supplied driver + store + config. The IDGenerator is the default uuid-v4 minter; tests inject a deterministic one via the exported field for stable frame ids. The cross-thread approval read store is wired separately via SetApprovalStore (optional, kept off the constructor so the existing NewServer callers/tests stay unchanged — D-A2-02 narrow seams).

func (*Server) Mux

func (s *Server) Mux() http.Handler

Mux registers the two routes using Go 1.22+ method-pattern routing (no chi/gorilla — matches the no-router codebase posture). When CORSPermissive is on (the dev knob) the handler is wrapped in withCORS so a browser cross-origin POST works end to end: the preflight OPTIONS is answered and ACAO is set on every response, including errors.

func (*Server) SetApprovalStore added in v1.0.0

func (s *Server) SetApprovalStore(store ApprovalStore)

SetApprovalStore wires the cross-thread HITL read store (APRV-01). It is set by the daemon composition root after NewServer; until set, the approvals read route answers 503 (the resolve route only needs the Runner and works regardless).

func (*Server) SetAssetService added in v1.0.0

func (s *Server) SetAssetService(service AssetService)

SetAssetService wires the upload/finalize/list asset API used by web and channels.

func (*Server) SetGovernanceProviders added in v1.0.1

func (s *Server) SetGovernanceProviders(p GovernanceProviders)

SetGovernanceProviders wires the read-only governance board providers (GOV-01/02/03). Set by the daemon composition root after NewServer; until set, the governance board routes (registered in Plan 02) answer 503. Kept off the constructor so existing NewServer callers/tests stay unchanged (D-A2-02 narrow seam).

func (*Server) SetGraphView added in v1.0.0

func (s *Server) SetGraphView(gv GraphView)

SetGraphView wires the read-only Phase-27 graph normalizer (GRAPH-01) the /api/graph/schema + /api/graph/query routes delegate to. Set by the daemon composition root after NewServer (boot-time knowledge.Client → knowledge.NewGraphView); until set, both routes answer 503 (a missing graph client must not abort serve boot). Kept off the constructor so existing NewServer callers/tests stay unchanged (D-A2-02 narrow seam).

func (*Server) SetImageProxy added in v1.0.0

func (s *Server) SetImageProxy(images ImageFetcher)

SetImageProxy wires the SSRF-safe image fetcher (D-09) the /api/image-proxy route delegates to. Set by the daemon composition root after NewServer (the *web.Client already wired for web_search/web_fetch); until set, the route answers 503. Kept off the constructor so existing NewServer callers/tests stay unchanged (D-A2-02).

func (*Server) SetOnboardingService added in v1.0.1

func (s *Server) SetOnboardingService(svc OnboardingService)

SetOnboardingService wires the onboarding provisioning + interview service (ONBD-01/ 02). Set by the daemon composition root after NewServer; until set, the onboarding routes (registered in Plan 05) answer 503. Kept off the constructor so existing NewServer callers/tests stay unchanged (D-A2-02 narrow seam).

type ServerConfig

type ServerConfig struct {
	CORSPermissive  bool
	BufferCap       int
	HealthCheck     func(context.Context) error
	HealthDetails   func() map[string]any
	ReadinessProbes []ReadinessProbe
}

ServerConfig carries the AG-UI server knobs the daemon resolves from config (AURA_AGUI_*). CORSPermissive gates the `Access-Control-Allow-Origin: *` header (default restrictive, T-12-13); BufferCap is the cap of the per-connection SSE pump channel (drop-on-full, never block the Loop, T-12-09). A non-positive BufferCap falls back to the fanout default. ReadinessProbes are the required-dependency checks /readyz runs (O-05/AP-14): /healthz stays a cheap LIVENESS check, /readyz reflects whether the required backends (PG + Neo4j) are reachable. An empty list reports ready (the daemon was started without gated deps).

type SkillsBoardProvider added in v1.0.1

type SkillsBoardProvider interface {
	ActiveSkills() []skills.Skill
	StageSkills(stage string) ([]skills.StageSkill, error)
	AuditLog(ctx context.Context, filter skills.AuditFilter) ([]skills.AuditRow, error)
}

SkillsBoardProvider is the read-only skills-governance surface (GOV-02): the active snapshot (Loader.List), the per-stage pending/archived metadata (ListStage — never a mounted body), and the append-only audit ledger (newest-first). The pending/archived reader returns metadata only, so a pending body never enters context by construction.

Jump to

Keyboard shortcuts

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