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 ¶
- func BriefTodayHandler(w http.ResponseWriter, r *http.Request)
- func ChatHandler(w http.ResponseWriter, r *http.Request)
- func SetBriefingImplementations(gen BriefGenerator)
- func SetImplementations(runner ChatRunner, sink SessionSink)
- func UIHandler(w http.ResponseWriter, r *http.Request)
- type APODInput
- type APODResult
- type Brief
- type BriefGenerator
- type BriefSection
- type BriefingGenerator
- type ChatAgent
- type ChatRunner
- type EarthEvent
- type EarthEventCategory
- type EarthEventsInput
- type EarthEventsResult
- type EarthPhotoInput
- type EarthPhotoResult
- type MarsInput
- type MarsPhoto
- type MarsResult
- type NEOApproach
- type NEOInput
- type NEOResult
- type SessionSink
- type SessionStore
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.
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 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 ¶
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.
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 ¶
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 ¶
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 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 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 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 ¶
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.