Files
query-orchestration/internal/bot/models.go
T

250 lines
8.8 KiB
Go
Raw Normal View History

package bot
import (
"encoding/json"
"time"
"github.com/google/uuid"
)
// Session is the service-layer representation of a chatbot session.
// Handlers translate this into BotSessionResponse for the wire. Fields
// match plan §3 / §4: lastTurn = last_terminal_ordinal (the highest
// ordinal whose status is not in_flight), never the in-flight ordinal.
type Session struct {
ID uuid.UUID
ClientID string
CreatedBy string
Title string
State json.RawMessage
LastTurn int32
CreatedAt time.Time
UpdatedAt time.Time
Scope SessionScope
RecentTurns []TurnPreview
IsDeleted bool
}
// SessionScope is the persisted (documents, folders) tuple. Returned as
// id arrays per plan §3 — folder expansion to documents lives in the
// M3 FastAPI payload assembly path, not here.
type SessionScope struct {
DocumentIDs []uuid.UUID
FolderIDs []uuid.UUID
}
// TurnPreview is a single recent-turn row surfaced in the session-list
// response. M2 returns the columns the UI needs for a preview card.
type TurnPreview struct {
Ordinal int32
Prompt string
Completion *string
Status string
CreatedAt time.Time
}
// ListSessionsInput drives ListSessionsForOwner / ListSessionsForSuperAdmin.
// Limit and Offset are validated up-front by the handler; the service
// re-validates as defense in depth so non-HTTP callers cannot bypass
// the [1, 200] / >= 0 bounds plan §3 mandates.
type ListSessionsInput struct {
Limit int
Offset int
}
// PatchScopeInput carries the body of PATCH /scope. The service
// deduplicates each array, rejects an id that appears in both add and
// remove sets, and rejects cross-client ids.
type PatchScopeInput struct {
AddDocuments []uuid.UUID
RemoveDocuments []uuid.UUID
AddFolders []uuid.UUID
RemoveFolders []uuid.UUID
}
// ---------- M3: FastAPI agent service wire types ----------
//
// Field shapes are pinned by plans/chatbot.stuff/openapi.chatbot.swagger.json
// (the "Aaria Agent Service" v0.1.0 contract). Plan §7 defines the
// request/response mapping; these structs serialize 1:1 to the swagger
// schema with snake_case JSON tags.
// ChatRequest is the body sent to POST /agent/chat. Plan §7 mapping
// table: request_id is "{sessionId}:{ordinal}" stable across retries;
// user_id is the Cognito sub treated as opaque telemetry; session_id is
// bot_sessions.id as a string; conversation_history is the last N
// completed turns × 2 messages; session_state is bot_sessions.state from
// tx1 (nil when the session has no state); scope is the assembled
// document/folder set.
type ChatRequest struct {
RequestID string `json:"request_id"`
UserID string `json:"user_id"`
SessionID string `json:"session_id"`
Message string `json:"message"`
ConversationHistory []ConversationMessage `json:"conversation_history"`
SessionState *SessionState `json:"session_state,omitempty"`
Scope ChatScope `json:"scope"`
}
// ChatResponse is the body returned by POST /agent/chat. Plan §7
// mandates that updated_session_state == null preserves the prior state
// and an explicit {} overwrites it; the *json.RawMessage type is the
// standard Go idiom for distinguishing those two cases at unmarshal.
type ChatResponse struct {
RequestID string `json:"request_id"`
Status string `json:"status"`
Answer *Answer `json:"answer,omitempty"`
UpdatedSessionState *json.RawMessage `json:"updated_session_state,omitempty"`
Metadata *Metadata `json:"metadata,omitempty"`
Error *ErrorPayload `json:"error,omitempty"`
}
// ChatScope is the assembled document/folder payload sent to FastAPI.
// Both fields are emitted as non-null arrays (the swagger marks them
// required) — empty input produces "[]", not "null".
type ChatScope struct {
DocumentIDs []string `json:"document_ids"`
FolderIDs []string `json:"folder_ids"`
}
// ConversationMessage is one entry in conversation_history. Plan §7:
// each completed turn produces a (user, assistant) pair with the turn's
// created_at as the timestamp.
type ConversationMessage struct {
Role string `json:"role"`
Content string `json:"content"`
Timestamp time.Time `json:"timestamp"`
}
// Answer is the agent's response payload. v1 plan §3 mandates that
// citations live in answer.text markdown; structured_data and
// follow_up_suggestions are response-only fields (logged or surfaced,
// not persisted).
type Answer struct {
Text string `json:"text"`
ResultType string `json:"result_type"`
StructuredData json.RawMessage `json:"structured_data,omitempty"`
FollowUpSuggestions []string `json:"follow_up_suggestions,omitempty"`
Confidence float64 `json:"confidence"`
}
// Metadata is the per-call telemetry from the agent service. The
// authoritative swagger declares Agents as a MAP keyed by agent name
// (NOT an array). SummarizeMetadata sums tokens by iterating map values.
type Metadata struct {
TotalLatencyMs int `json:"total_latency_ms"`
RetryCount int `json:"retry_count"`
Agents map[string]AgentTelemetry `json:"agents"`
}
// AgentTelemetry is one entry in the Metadata.Agents map. Passed is a
// pointer so the field can round-trip null → unset as the swagger allows.
type AgentTelemetry struct {
LatencyMs int `json:"latency_ms"`
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
Passed *bool `json:"passed,omitempty"`
}
// ErrorPayload is the error-shape returned by FastAPI for a 200 response
// with status:"error" or carried alongside the error sentinel surfaced
// by the client when the upstream fails the application path.
type ErrorPayload struct {
Code string `json:"code"`
Message string `json:"message"`
}
// HealthResponse is the body returned by GET /health. Plan §7 mandates
// status == "ok" plus non-empty service and version.
type HealthResponse struct {
Status string `json:"status"`
Service string `json:"service"`
Version string `json:"version"`
}
// SessionState is the agent-side session-state object. The swagger
// defines it as an open object with no enumerated keys; we use a
// json.RawMessage alias so the client passes the bytes through
// untouched and the M4 tx2 path can apply StripCachedResults /
// SessionStateOversize gates without re-decoding. M4 may refine if
// structured access becomes useful.
type SessionState = json.RawMessage
// ChatCallResult is what FastAPIClient.Chat returns on success. The
// service layer reads SentAt/ReceivedAt for the wall-clock fallback when
// metadata is absent (plan §7 mapping table) and AttemptCount when
// persisting bot_turns.error per plan §6.
type ChatCallResult struct {
Response *ChatResponse
SentAt time.Time
ReceivedAt time.Time
AttemptCount int
}
// AttemptCounter is implemented by every error returned by
// FastAPIClient.Chat so the service layer can record the attempt count
// in bot_turns.error without unwrapping driver-specific types. Plan §7
// mandates the error path expose AttemptCount alongside the result path.
type AttemptCounter interface {
error
AttemptCount() int
}
// ---------- M4: AddTurn types ----------
// AddTurnInput drives Service.AddTurn. Caller specifies Ordinal: the
// handler computes it from session state via prompt-match semantics
// before calling the service; the concurrency test passes Ordinal
// directly. Plan §6 algorithm is by-ordinal.
type AddTurnInput struct {
SessionID uuid.UUID
ClientID string
Actor string
Ordinal int
Prompt string
}
// AddTurnResult is the per-call outcome the handler converts into
// BotTurnResponse. Status is one of the bot_turns.status enum values.
// Error is the persisted error JSON when status in (errored, abandoned,
// session_deleted), nil otherwise.
type AddTurnResult struct {
Ordinal int32
Status string
Completion *string
LatencyMs int
TokensIn int
TokensOut int
Error []byte
AttemptCount int
CreatedAt time.Time
}
// CompleteTurnInput is the argument shape for Service.CompleteBotTurnWithAttempt.
// Plan §11 stale-tx2 test exercises this directly so a stale callback
// (whose AttemptID does not match the persisted row) returns
// ErrTurnSuperseded.
type CompleteTurnInput struct {
SessionID uuid.UUID
Ordinal int
AttemptID uuid.UUID
Completion string
LatencyMs int
TokensIn int
TokensOut int
}
// Turn is the service-layer representation of one bot_turns row. The
// handler converts this into BotTurnResponse for the wire.
type Turn struct {
Ordinal int32
Prompt string
Completion *string
Status string
LatencyMs *int32
TokensIn *int32
TokensOut *int32
Error []byte
CreatedAt time.Time
}