Help us shape the future of Go! Take the 2026 Go Developer Survey

app

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: MIT Imports: 25 Imported by: 0

Documentation

Overview

Package app owns everything except process bootstrap: agent wiring, HTTP handlers, session store, stubs, and the embedded UI. main.go stays tiny — it only detects stub-mode, builds the dependencies, calls SetImplementations, and starts the worker.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BriefTodayHandler

func BriefTodayHandler(w http.ResponseWriter, r *http.Request)

BriefTodayHandler serves GET /api/brief/today.

func ChatHandler

func ChatHandler(w http.ResponseWriter, r *http.Request)

ChatHandler multiplexes on HTTP method so all three verbs share a single Functions route registration (chat/{conversationId}).

func SetBriefingImplementations

func SetBriefingImplementations(gen BriefGenerator)

SetBriefingImplementations wires the runtime dependency used by BriefTodayHandler. Called from main.go alongside SetImplementations.

func SetImplementations

func SetImplementations(runner ChatRunner, sink SessionSink)

SetImplementations wires the runtime dependencies used by ChatHandler and UIHandler. Must be called before FunctionApp registration in main.

func UIHandler

func UIHandler(w http.ResponseWriter, r *http.Request)

Types

type APODInput

type APODInput struct {
	// Date in YYYY-MM-DD format. Leave empty for today's picture.
	Date string `json:"date,omitempty"`
}

type APODResult

type APODResult struct {
	Date        string `json:"date"`
	Title       string `json:"title"`
	Explanation string `json:"explanation"`
	URL         string `json:"url"`
	HDURL       string `json:"hdurl,omitempty"`
	MediaType   string `json:"media_type"`
	Copyright   string `json:"copyright,omitempty"`
}

type Brief

type Brief struct {
	ID          string       `json:"id"`          // YYYY-MM-DD (UTC)
	GeneratedAt string       `json:"generatedAt"` // RFC3339
	Headline    BriefSection `json:"headline"`
	EarthWatch  BriefSection `json:"earthWatch"`
	PhotoOfDay  BriefSection `json:"photoOfDay"`
	DidYouKnow  BriefSection `json:"didYouKnow"`
}

Brief is the JSON shape written to Cosmos and returned to the UI. Kept intentionally flat and small — the UI renders four cards and the brief text itself is what a reader cares about; the raw source data behind each section is discarded to keep the doc lean.

type BriefGenerator

type BriefGenerator interface {
	Generate(ctx context.Context, date string) (*Brief, error)
}

BriefGenerator produces a Brief for a given date. The real implementation drives the multi-agent editor pipeline (briefing_agent.go); the stub returns fixed sample content (stub.go) so /api/brief/today works with no Azure resources.

func NewStubBriefGenerator

func NewStubBriefGenerator() BriefGenerator

NewStubBriefGenerator returns a BriefGenerator that returns a fixed sample brief with a stable image URL, so the Daily Brief tab renders in stub mode without hitting Azure OpenAI or NASA.

type BriefSection

type BriefSection struct {
	Title    string `json:"title"`
	Body     string `json:"body"`
	ImageURL string `json:"imageUrl,omitempty"`
}

BriefSection is the shape of one card. ImageURL is optional and only populated for the photo-of-day section today.

type BriefingGenerator

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

BriefingGenerator drives the multi-agent pipeline. Build once per process; the underlying Azure OpenAI client is safe to share across concurrent Generate calls.

func NewBriefingGenerator

func NewBriefingGenerator(cred azcore.TokenCredential) (*BriefingGenerator, error)

NewBriefingGenerator wires the specialists + editor against the same Azure OpenAI endpoint the chat agent uses.

func (*BriefingGenerator) Generate

func (g *BriefingGenerator) Generate(ctx context.Context, date string) (*Brief, error)

Generate runs the editor once and parses its JSON response.

type ChatAgent

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

ChatAgent bundles the MAF agent with helpers for session hydration. One place owns the "how do I build an agent" wiring so main() stays focused on Function App concerns.

func NewChatAgent

func NewChatAgent(cred azcore.TokenCredential) (*ChatAgent, error)

NewChatAgent builds an Azure-OpenAI-backed MAF agent authenticated with AAD. The endpoint + deployment name come from env vars set by the Bicep infra (or from local.settings.json for local dev).

func (*ChatAgent) Agent

func (c *ChatAgent) Agent() *agent.Agent

Agent exposes the underlying MAF agent for callers that need the raw handle (e.g. tests). Handlers should prefer Run, which owns the streaming type-switch that turns MAF events into SSE frames.

func (*ChatAgent) LoadSession

func (c *ChatAgent) LoadSession(ctx context.Context, raw []byte) (*agent.Session, error)

LoadSession returns a MAF Session hydrated from stored JSON, or a fresh one for the first message in a conversation.

The key insight for serverless: agent.Session implements json.Marshaler/Unmarshaler, so the entire conversational state round-trips through any document store. There is no need to keep a chat process alive between turns — Cosmos is the source of truth.

func (*ChatAgent) Run

func (c *ChatAgent) Run(
	ctx context.Context,
	sess *agent.Session,
	userMessage string,
	emit func(event string, payload any) error,
) error

Run drives one turn against userMessage and forwards each ResponseUpdate's contents to the appropriate SSE frame. This is the method that makes *ChatAgent satisfy ChatRunner. The frame vocabulary matches what the UI in ui/index.html expects:

text        -> a delta of assistant text
tool_call   -> the model requested a tool invocation
tool_result -> the tool returned (autocall middleware handled it)

The MAF autocall middleware transparently invokes tools and feeds results back to the model before the loop ends, so we don't have to do anything special beyond observing the FunctionCall/Result content types as they stream by.

type ChatRunner

type ChatRunner interface {
	// LoadSession returns a Session hydrated from the given bytes,
	// or a fresh Session when raw is empty.
	LoadSession(ctx context.Context, raw []byte) (*agent.Session, error)

	// Run drives one turn of the agent against userMessage, mutating
	// sess in place and emitting SSE frames via emit(event, payload).
	// Frame vocabulary: "text", "tool_call", "tool_result".
	Run(ctx context.Context, sess *agent.Session, userMessage string, emit func(event string, payload any) error) error
}

ChatRunner is the surface the HTTP handler depends on. Both the real MAF-backed implementation (agent.go) and the stub used for offline smoke tests (stub.go) satisfy it. Extracting the interface keeps handler.go free of runtime-mode branching.

func NewStubChat

func NewStubChat() ChatRunner

NewStubChat returns a ChatRunner that fakes tool calls and streams canned replies. Used when AZURE_OPENAI_ENDPOINT is not set — see main.go's stub-mode detection.

type EarthEvent

type EarthEvent struct {
	ID         string               `json:"id"`
	Title      string               `json:"title"`
	Link       string               `json:"link"`
	Categories []EarthEventCategory `json:"categories"`
}

type EarthEventCategory

type EarthEventCategory struct {
	Title string `json:"title"`
}

type EarthEventsInput

type EarthEventsInput struct {
	// One of: "open" (active) or "closed". Defaults to "open".
	Status string `json:"status,omitempty"`
}

type EarthEventsResult

type EarthEventsResult struct {
	Events []EarthEvent `json:"events"`
}

type EarthPhotoInput

type EarthPhotoInput struct{}

type EarthPhotoResult

type EarthPhotoResult struct {
	Caption string `json:"caption,omitempty"`
	Date    string `json:"date"`
	// ImageURL is a fully-formed URL you can drop into an <img> tag.
	ImageURL string `json:"image_url"`
}

type MarsInput

type MarsInput struct {
	// Rover name: curiosity | perseverance | opportunity | spirit. Default: curiosity.
	Rover string `json:"rover,omitempty"`
	// Martian sol (day). Mutually exclusive with earth_date.
	Sol int `json:"sol,omitempty"`
	// Earth date in YYYY-MM-DD format. Mutually exclusive with sol.
	EarthDate string `json:"earth_date,omitempty"`
	// Optional camera abbreviation (FHAZ, RHAZ, MAST, CHEMCAM, NAVCAM, ...).
	Camera string `json:"camera,omitempty"`
}

type MarsPhoto

type MarsPhoto struct {
	ID          int    `json:"id"`
	ImageURL    string `json:"img_src"`
	EarthDate   string `json:"earth_date"`
	Sol         int    `json:"sol"`
	Camera      string `json:"camera"`
	Rover       string `json:"rover"`
	RoverStatus string `json:"rover_status"`
}

type MarsResult

type MarsResult struct {
	Photos []MarsPhoto `json:"photos"`
}

type NEOApproach

type NEOApproach struct {
	Name              string  `json:"name"`
	CloseApproachDate string  `json:"close_approach_date"`
	MissDistanceKm    float64 `json:"miss_distance_km"`
	RelativeVelKps    float64 `json:"relative_velocity_kps"`
	DiameterMinMeters float64 `json:"diameter_min_meters"`
	DiameterMaxMeters float64 `json:"diameter_max_meters"`
	Hazardous         bool    `json:"potentially_hazardous"`
	NASAJPLURL        string  `json:"nasa_jpl_url"`
}

type NEOInput

type NEOInput struct {
	// Start date in YYYY-MM-DD format. Defaults to today.
	StartDate string `json:"start_date,omitempty"`
	// End date in YYYY-MM-DD format. Max 7 days from start. Defaults to start_date.
	EndDate string `json:"end_date,omitempty"`
}

type NEOResult

type NEOResult struct {
	ElementCount int           `json:"element_count"`
	Approaches   []NEOApproach `json:"approaches"`
}

type SessionSink

type SessionSink interface {
	Load(ctx context.Context, conversationID string) ([]byte, error)
	Save(ctx context.Context, conversationID string, sess *agent.Session) error
	Delete(ctx context.Context, conversationID string) error
}

SessionSink abstracts the persistence layer so handler.go can be tested (and run offline) against an in-memory map. The real implementation lives in store.go; the stub lives in stub.go.

func NewStubStore

func NewStubStore() SessionSink

NewStubStore returns an in-memory SessionSink for offline demos. State lives in the process, so it's lost on restart — fine for a smoke test, unsuitable for anything else.

type SessionStore

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

SessionStore persists MAF sessions in Cosmos DB, keyed by conversationId. Each Function invocation reads the document, hands the session to the agent, then writes the mutated session back — the classic serverless "load / mutate / save" cycle around an otherwise stateless worker.

func NewSessionStore

func NewSessionStore(ctx context.Context, cred azcore.TokenCredential) (*SessionStore, error)

func (*SessionStore) Delete

func (s *SessionStore) Delete(ctx context.Context, conversationID string) error

func (*SessionStore) Load

func (s *SessionStore) Load(ctx context.Context, conversationID string) ([]byte, error)

Load returns the raw session JSON for a conversation, or nil bytes if the conversation doesn't exist yet. 404s are NOT treated as an error: the first message in a new conversation is a normal case, not a failure.

func (*SessionStore) Save

func (s *SessionStore) Save(ctx context.Context, conversationID string, sess *agent.Session) error

Save upserts (rather than creates) so the endpoint is idempotent across retries — a repeated POST with the same conversationId on a transient error won't fail with "already exists".

Jump to

Keyboard shortcuts

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