Documentation
¶
Index ¶
- type ChatMessage
- type ChatTitle
- type EncryptionService
- type FirestoreClient
- func (f *FirestoreClient) GetMessage(ctx context.Context, userID, chatID, messageID string) (*ChatMessage, error)
- func (f *FirestoreClient) GetResponseID(ctx context.Context, userID, chatID string) (string, error)
- func (f *FirestoreClient) GetUserPublicKey(ctx context.Context, userID string) (*UserPublicKey, error)
- func (f *FirestoreClient) SaveChatTitle(ctx context.Context, userID, chatID string, title *ChatTitle) error
- func (f *FirestoreClient) SaveMessage(ctx context.Context, userID string, msg *ChatMessage) error
- func (f *FirestoreClient) SaveResponseID(ctx context.Context, userID, chatID, responseID string) error
- func (f *FirestoreClient) UpdateMessage(ctx context.Context, userID, chatID, messageID string, ...) error
- func (f *FirestoreClient) VerifyChatOwnership(ctx context.Context, userID, chatID string) error
- type JWKPublicKey
- type MessageToStore
- type Service
- func (s *Service) EncryptContent(content string, publicKeyJWK string) (string, error)
- func (s *Service) GetPublicKey(ctx context.Context, userID string) (*UserPublicKey, error)
- func (s *Service) GetResponseID(ctx context.Context, userID, chatID string) (string, error)
- func (s *Service) SaveResponseID(ctx context.Context, userID, chatID, responseID string) error
- func (s *Service) SaveThinkingMessage(ctx context.Context, userID, chatID, messageID, model string, ...) error
- func (s *Service) Shutdown()
- func (s *Service) StoreMessageAsync(ctx context.Context, msg MessageToStore) error
- func (s *Service) UpdateGenerationStateSync(ctx context.Context, userID, chatID, messageID, state, errorMsg string) error
- func (s *Service) UpdateMessageGenerationState(ctx context.Context, userID, chatID, messageID, state, errorMsg string) error
- type UserPublicKey
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ChatMessage ¶
type ChatMessage struct {
ID string `firestore:"id"` // Message UUID
EncryptedContent string `firestore:"encryptedContent"` // Encrypted message content
IsFromUser bool `firestore:"isFromUser"` // true = user, false = assistant
ChatID string `firestore:"chatId"` // Chat UUID
IsError bool `firestore:"isError"` // true if error occurred
Timestamp time.Time `firestore:"timestamp"` // Message timestamp
PublicEncryptionKey string `firestore:"publicEncryptionKey"` // Public key used (JSON string or "none")
// Stop control fields (for AI responses that were stopped mid-generation)
Stopped bool `firestore:"stopped,omitempty"` // true if generation was stopped by user/system
StoppedBy string `firestore:"stoppedBy,omitempty"` // User ID who stopped, or "system_timeout"/"system_shutdown"
StopReason string `firestore:"stopReason,omitempty"` // Why stopped: "user_cancelled", "timeout", "error", "system_shutdown"
// Generation state tracking (for GPT-5 Pro and other long-running models)
Model string `firestore:"model,omitempty"` // Model ID (e.g., "gpt-5-pro")
GenerationState string `firestore:"generationState,omitempty"` // "thinking", "completed", "failed"
GenerationStartedAt time.Time `firestore:"generationStartedAt,omitempty"` // When generation started
GenerationCompletedAt time.Time `firestore:"generationCompletedAt,omitempty"` // When generation completed/failed
GenerationError string `firestore:"generationError,omitempty"` // Error message if failed
// Anonymizer: encrypted replacement map (original→replacement) for PII redaction
EncryptedMaskedKeywords string `firestore:"encryptedMaskedKeywords,omitempty"`
}
ChatMessage represents a stored chat message in Firestore
type ChatTitle ¶
type ChatTitle struct {
Title string `firestore:"title,omitempty"` // Plaintext title (only when encryption disabled)
EncryptedTitle string `firestore:"encryptedTitle,omitempty"` // Encrypted title (only when encryption enabled)
TitlePublicEncryptionKey string `firestore:"titlePublicEncryptionKey,omitempty"` // Public key used (only when encrypted)
UpdatedAt time.Time `firestore:"updatedAt"` // Last update timestamp
}
ChatTitle represents a stored chat title in Firestore IMPORTANT: Only ONE of Title or EncryptedTitle should be set, never both
type EncryptionService ¶
type EncryptionService struct{}
EncryptionService handles ECDH + AES-GCM message encryption
func NewEncryptionService ¶
func NewEncryptionService() *EncryptionService
NewEncryptionService creates a new encryption service
func (*EncryptionService) EncryptMessage ¶
func (e *EncryptionService) EncryptMessage(content string, publicKeyJWK string) (string, error)
EncryptMessage encrypts message content using ECDH + HKDF + AES-256-GCM Returns base64-encoded: ephemeralPublicKey || nonce || ciphertext || tag
func (*EncryptionService) ValidatePublicKey ¶
func (e *EncryptionService) ValidatePublicKey(publicKeyJWK string) error
ValidatePublicKey validates a JWK public key
type FirestoreClient ¶
type FirestoreClient struct {
// contains filtered or unexported fields
}
FirestoreClient handles Firestore operations for messages
func NewFirestoreClient ¶
func NewFirestoreClient(client *firestore.Client) *FirestoreClient
NewFirestoreClient creates a new Firestore client wrapper
func (*FirestoreClient) GetMessage ¶
func (f *FirestoreClient) GetMessage(ctx context.Context, userID, chatID, messageID string) (*ChatMessage, error)
GetMessage retrieves a message from Firestore
func (*FirestoreClient) GetResponseID ¶
GetResponseID retrieves the latest OpenAI Responses API response_id for a chat. This is used for continuing conversations with GPT-5 Pro and other stateful models.
Parameters:
- ctx: Context for the operation
- userID: User ID who owns the chat
- chatID: Chat ID
Returns:
- string: The response_id (e.g., "resp_abc123"), or empty string if not found
- error: If retrieval failed (network error, permission denied, etc.)
Note: Returns empty string (not error) if chat exists but has no lastResponseId field. This is normal for chats that haven't used Responses API yet.
func (*FirestoreClient) GetUserPublicKey ¶
func (f *FirestoreClient) GetUserPublicKey(ctx context.Context, userID string) (*UserPublicKey, error)
GetUserPublicKey retrieves a user's public key Path: /users/{userId} -> accountKey field
func (*FirestoreClient) SaveChatTitle ¶
func (f *FirestoreClient) SaveChatTitle(ctx context.Context, userID, chatID string, title *ChatTitle) error
SaveChatTitle saves/updates chat title (plaintext or encrypted) Path: /users/{userId}/chats/{chatId} IMPORTANT: This only UPDATES existing chat documents, does not create new ones IMPORTANT: Only ONE of Title or EncryptedTitle should be set, never both
func (*FirestoreClient) SaveMessage ¶
func (f *FirestoreClient) SaveMessage(ctx context.Context, userID string, msg *ChatMessage) error
SaveMessage saves an encrypted message to Firestore Path: /chats/{userId}/{chatId}/messages/{messageId}
func (*FirestoreClient) SaveResponseID ¶
func (f *FirestoreClient) SaveResponseID(ctx context.Context, userID, chatID, responseID string) error
SaveResponseID stores the latest OpenAI Responses API response_id for a chat. This is used for continuing conversations with GPT-5 Pro and other stateful models.
Parameters:
- ctx: Context for the operation
- userID: User ID who owns the chat
- chatID: Chat ID
- responseID: The response_id from OpenAI (e.g., "resp_abc123")
Path: /users/{userId}/chats/{chatId} Field: lastResponseId (string)
Returns:
- error: If save failed
Note: This updates an existing chat document. If the chat doesn't exist, the update will fail gracefully (returns FailedPrecondition error).
func (*FirestoreClient) UpdateMessage ¶
func (f *FirestoreClient) UpdateMessage(ctx context.Context, userID, chatID, messageID string, updates map[string]interface{}) error
UpdateMessage updates specific fields of an existing message in Firestore. This is used to update generation state without overwriting the entire message.
Parameters:
- ctx: Context for the operation
- userID: User ID who owns the message
- chatID: Chat ID
- messageID: Message ID
- updates: Map of field paths to new values (e.g., {"generationState": "completed"})
Returns:
- error: If update failed
Path: /users/{userId}/chats/{chatId}/messages/{messageId}
func (*FirestoreClient) VerifyChatOwnership ¶
func (f *FirestoreClient) VerifyChatOwnership(ctx context.Context, userID, chatID string) error
VerifyChatOwnership checks if a user owns a specific chat Returns nil if user owns the chat, error otherwise
type JWKPublicKey ¶
type JWKPublicKey struct {
Crv string `json:"crv"` // "P-256"
Ext bool `json:"ext"` // true
KeyOps []string `json:"key_ops"` // []
Kty string `json:"kty"` // "EC"
X string `json:"x"` // Base64url-encoded X coordinate
Y string `json:"y"` // Base64url-encoded Y coordinate
}
JWKPublicKey represents the parsed JWK public key
type MessageToStore ¶
type MessageToStore struct {
UserID string
ChatID string
MessageID string
IsFromUser bool
Content string // Plaintext content to be encrypted
IsError bool
EncryptionEnabled *bool // nil = not specified (backward compat), true = enforce encryption, false = store plaintext
// Stop control (for streaming broadcast feature)
Stopped bool // true if generation was stopped mid-stream
StoppedBy string // User ID who stopped, or "system_timeout"/"system_shutdown"
StopReason string // Why stopped: "user_cancelled", "timeout", "error", "system_shutdown"
// Model and generation state (for GPT-5 Pro long-running generation tracking)
Model string // Model ID (e.g., "gpt-5-pro")
GenerationState string // "thinking", "completed", "failed"
GenerationStartedAt *time.Time
GenerationCompletedAt *time.Time
GenerationError string
// Anonymizer replacement map JSON (e.g. [{"original":"John","replacement":"Mark"}])
MaskedKeywords string
}
MessageToStore is the internal representation for messages to be stored
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service handles async message storage with encryption
func NewService ¶
NewService creates a new message storage service
func (*Service) EncryptContent ¶
EncryptContent exposes encryption function for title service
func (*Service) GetPublicKey ¶
GetPublicKey exposes getPublicKey for title service
func (*Service) GetResponseID ¶
GetResponseID retrieves the latest OpenAI Responses API response_id for a chat. This is used for continuing conversations with GPT-5 Pro and other stateful models.
Parameters:
- ctx: Context for the operation
- userID: User ID who owns the chat
- chatID: Chat ID
Returns:
- string: The response_id (e.g., "resp_abc123"), or empty string if not found
- error: If retrieval failed
func (*Service) SaveResponseID ¶
SaveResponseID stores the latest OpenAI Responses API response_id for a chat. This is used for continuing conversations with GPT-5 Pro and other stateful models.
Parameters:
- ctx: Context for the operation
- userID: User ID who owns the chat
- chatID: Chat ID
- responseID: The response_id from OpenAI (e.g., "resp_abc123")
Returns:
- error: If save failed
func (*Service) SaveThinkingMessage ¶
func (s *Service) SaveThinkingMessage(ctx context.Context, userID, chatID, messageID, model string, encryptionEnabled *bool) error
SaveThinkingMessage saves a placeholder message for long-running generations (GPT-5 Pro). This allows clients to detect in-progress generation when reconnecting.
The message is saved immediately when streaming starts with:
- generationState: "thinking"
- encryptedContent: empty (will be updated when complete)
- generationStartedAt: current timestamp
Parameters:
- ctx: Context for the operation
- userID: User ID who owns the message
- chatID: Chat ID
- messageID: AI message ID
- model: Model ID (e.g., "gpt-5-pro")
- encryptionEnabled: Whether to encrypt (can be nil for backward compat)
Returns:
- error: If save failed
func (*Service) StoreMessageAsync ¶
func (s *Service) StoreMessageAsync(ctx context.Context, msg MessageToStore) error
StoreMessageAsync queues a message for async storage
func (*Service) UpdateGenerationStateSync ¶
func (s *Service) UpdateGenerationStateSync(ctx context.Context, userID, chatID, messageID, state, errorMsg string) error
UpdateGenerationStateSync updates a message's generation state synchronously.
This is used by the background polling worker to update Firestore state as OpenAI's response status changes.
Unlike StoreMessageAsync, this method updates Firestore directly without going through the async worker queue. This ensures critical state transitions (thinking → completed/failed) are saved immediately.
Parameters:
- ctx: Context for the operation
- userID: User ID who owns the message
- chatID: Chat ID
- messageID: AI message ID
- state: New state ("thinking", "completed", "failed")
- errorMsg: Error message (only if state="failed")
Returns:
- error: If update failed
func (*Service) UpdateMessageGenerationState ¶
func (s *Service) UpdateMessageGenerationState(ctx context.Context, userID, chatID, messageID, state, errorMsg string) error
UpdateMessageGenerationState updates a message's generation state. Used to mark messages as "completed" or "failed" after generation finishes.
This method updates an existing message in Firestore - it does NOT create a new message. The full message content should already be stored via the normal StoreMessageAsync flow.
Parameters:
- ctx: Context for the operation
- userID: User ID who owns the message
- chatID: Chat ID
- messageID: AI message ID
- state: New state ("completed" or "failed")
- errorMsg: Error message (only if state="failed")
Returns:
- error: If update failed
type UserPublicKey ¶
type UserPublicKey struct {
CreatedAt time.Time `firestore:"createdAt"`
Public string `firestore:"public"` // JWK JSON string (EC P-256)
UpdatedAt time.Time `firestore:"updatedAt"`
Version int `firestore:"version"` // Key version number
}
UserPublicKey represents a user's ECDSA P-256 public key