import { RpcMethod } from './commonTypes'; /** Role represents the sender of a message */ export type Role = 'ROLE_UNSPECIFIED' | 'ROLE_USER' | 'ROLE_ASSISTANT' /** Assistant message with tool calls (renamed for DB limit) */ | 'ROLE_TOOL_REQUEST' /** Function/tool response */ | 'ROLE_TOOL_USE_RESPONSE' /** Compactified context summary */ | 'ROLE_CONTEXT_COMPACTIFIED' /** Outcome of the user's decision on tool-call proposals; JSON content */ | 'ROLE_PROPOSAL_RESULT'; /** Message represents a single message in a chat */ export type Message = { id: number; content: string; role: Role; timestamp: Date; usage: UsageInfo; actions: SuggestedAction[]; /** * iteration_capped marks the row a turn left when it ran out of steps with work left; the * widget offers Continue on it. Its content may be empty. */ iterationCapped: boolean; }; /** * SuggestedAction represents a model-proposed follow-up action shown as a button. * id is a stable slug used for analytics; label is the button text. * Exactly one of prompt or url must be set: * - prompt: literal user message dispatched when the button is clicked. * - url: external link (e.g. documentation) opened when the button is clicked. * primary marks the visually emphasised CTA — at most one per actions block. */ export type SuggestedAction = { id: string; label: string; prompt: string; url: string; primary: boolean; }; /** Chat represents a conversation with its metadata and messages */ export type Chat = { id: number; title: string; messages: Message[]; createdAt: Date; updatedAt: Date; /** * is_onboarding flags this chat as the account's onboarding chat. * Frontend may render the chat with onboarding-specific UI. The same * onboarding agent runs on either inference provider — StreamInferenceLLM * (self-hosted Qwen) or StreamInference (Claude); the frontend picks by * which endpoint it calls for the chat. The opening turn is an ordinary * turn: the frontend sends a real first user message (e.g. a "Get started" * button prompt) and the onboarding agent replies with the greeting + * suggested actions. An empty message is rejected (InvalidArgument) — there * is no server-synthesised kickoff. */ isOnboarding: boolean; /** * onboarding_completed_at is set on the onboarding chat the moment the * agent invokes the complete_onboarding tool. Frontend checks this field * after each StreamInferenceLLM turn (and on chat reload) to detect that * the agent has signalled completion and the handoff to a regular chat * should be triggered via CompleteOnboardingHandoff. */ onboardingCompletedAt: Date; /** * handoff_child_chat_id, on the onboarding chat, stores the id of the * regular chat created by CompleteOnboardingHandoff. Non-zero means * the handoff has already been performed — repeated calls are idempotent * and return that chat. Zero means no handoff yet. */ handoffChildChatId: number; /** * active_turn_id names the turn still answering in this chat, empty when none is. Follow it * with AttachTurn; the chat stays busy until it ends. */ activeTurnId: string; /** * turn_user_message_id is the running turn's user message (GetChat only). Messages after it * belong to that turn and arrive through AttachTurn; 0 means it has not written one yet. */ turnUserMessageId: number; }; /** CreateChatRequest for creating a new chat */ export type CreateChatRequest = { application: string; title: string; /** * is_onboarding requests the onboarding chat for the calling account. * The server seeds the chat with the account's onboarding plan snapshot. * At most one onboarding chat exists per account: if one already exists, * the server returns it instead of creating a new chat. */ isOnboarding: boolean; }; /** CreateChatResponse returns the created chat */ export type CreateChatResponse = { chat: Chat; }; /** GetChatRequest for retrieving a specific chat */ export type GetChatRequest = { application: string; chatId: number; }; /** GetChatResponse returns the requested chat */ export type GetChatResponse = { chat: Chat; }; /** GetAllChatsRequest for retrieving all chats */ export type GetAllChatsRequest = { application: string; }; /** GetAllChatsResponse returns all chats sorted by updated_at */ export type GetAllChatsResponse = { chats: Chat[]; }; /** UpdateChatTitleRequest for updating a chat's title */ export type UpdateChatTitleRequest = { chatId: number; title: string; }; /** UpdateChatTitleResponse confirms the update */ export type UpdateChatTitleResponse = {}; /** DeleteChatRequest for deleting a chat */ export type DeleteChatRequest = { application: string; id: number; }; /** DeleteChatResponse confirms the deletion */ export type DeleteChatResponse = { success: boolean; }; /** ClearChatRequest for clearing all messages from a chat */ export type ClearChatRequest = { application: string; id: number; }; /** ClearChatResponse confirms the clearing */ export type ClearChatResponse = { success: boolean; }; /** CancelTurnRequest stops the turn running in a chat, on whichever pod it runs. */ export type CancelTurnRequest = { application: string; chatId: number; }; /** CancelTurnResponse.cancelled is false when the chat had no turn running. */ export type CancelTurnResponse = { cancelled: boolean; }; /** AttachTurnRequest follows a running turn — from its start, or after the last event seen. */ export type AttachTurnRequest = { chatId: number; /** Chat.active_turn_id */ turnId: string; /** InferenceResponse.event_id of the last event seen; empty replays all */ afterEventId: string; }; /** FileAttachment represents a file uploaded by the user */ export type FileAttachment = { filename: string; /** MIME type (e.g., "application/pdf") */ type: string; /** Base64-encoded file content */ base64: string; }; /** InferenceRequest for streaming AI inference */ export type InferenceRequest = { chatId: number; /** User message content to be added before inference */ message: string; /** Optional URL for context */ url: string; /** Optional file attachments */ attachments: FileAttachment[]; /** Optional custom system prompt to add to the inference */ customSystemPrompt: string; }; /** StreamingEvent types */ export type StreamEventType = 'STREAM_EVENT_UNSPECIFIED' | 'STREAM_EVENT_MESSAGE_START' | 'STREAM_EVENT_CONTENT_BLOCK_START' | 'STREAM_EVENT_TEXT_START' | 'STREAM_EVENT_TEXT_DELTA' | 'STREAM_EVENT_TOOL_USE_START' | 'STREAM_EVENT_CONTENT_BLOCK_STOP' | 'STREAM_EVENT_MESSAGE_DELTA' | 'STREAM_EVENT_MESSAGE_STOP' | 'STREAM_EVENT_ERROR' | 'STREAM_EVENT_TOOL_USE_RESULT' | 'STREAM_EVENT_TOOL_USE_DELTA' /** Chain of Thoughts thinking content */ | 'STREAM_EVENT_THINKING_DELTA' /** Model-proposed follow-up actions for the user */ | 'STREAM_EVENT_SUGGESTED_ACTIONS' /** An assistant tool call mutated data an open view may be showing — reload it. See ReloadView. */ | 'STREAM_EVENT_RELOAD_VIEW' /** A tool call became a proposal, or a proposal changed status. See Proposal. */ | 'STREAM_EVENT_PROPOSAL' /** The turn's tool calls landed in one row; see tool_request_message_id. */ | 'STREAM_EVENT_TOOL_REQUEST_SAVED'; /** * ReloadView tells the frontend that a tool call mutated data, so an open * view showing it should re-fetch (views listen for a "reload-view" event on * spaContext.eventBus; currently only web popup pages do). * entity: "form-web-popup" | "form-web-popups-content" | "list-web-popups". * code is the entity code for form-* views, empty for list-* views. */ export type ReloadView = { entity: string; code: string; }; /** Tool information for tool use events */ export type ToolInfo = { name: string; args: string; }; /** Usage information for token consumption tracking */ export type UsageInfo = { inputTokens: number; outputTokens: number; cacheCreationInputTokens: number; cacheReadInputTokens: number; totalTokens: number; percentage: number; }; /** InferenceResponse for streaming AI responses */ export type InferenceResponse = { type: StreamEventType; content: string; tool: ToolInfo; stopReason: string; stopSequence: string; error: string; usage: UsageInfo; /** Set on STREAM_EVENT_SUGGESTED_ACTIONS */ actions: SuggestedAction[]; /** Set on STREAM_EVENT_RELOAD_VIEW */ reloadView: ReloadView; /** Set by AttachTurn; pass the last one back as after_event_id to resume */ eventId: string; /** Set on STREAM_EVENT_PROPOSAL */ proposal: Proposal; /** Set on STREAM_EVENT_TOOL_REQUEST_SAVED */ toolRequestMessageId: number; }; export type GetConsentRequest = { application: string; }; export type GetConsentResponse = { consentGiven: boolean; }; export type GiveConsentRequest = { application: string; consentGiven: boolean; }; export type GiveConsentResponse = {}; /** CompactifyContextRequest for compactifying chat context */ export type CompactifyContextRequest = { application: string; chatId: number; }; /** CompactifyContextResponse returns success status */ export type CompactifyContextResponse = { success: boolean; /** The compactified summary */ compactifiedContent: string; }; /** CheckUsageAllowedRequest for checking if AI usage is allowed */ export type CheckUsageAllowedRequest = { application: string; }; /** CheckUsageAllowedResponse returns whether usage is allowed */ export type CheckUsageAllowedResponse = { allowed: boolean; reason: string; }; /** * GetOnboardingStatusRequest reports the calling account's onboarding state. * Onboarding is per-account (at most one onboarding chat per account); the * application is only used for routing and auth scoping. */ export type GetOnboardingStatusRequest = { application: string; }; /** * GetOnboardingStatusResponse lets the frontend decide whether to route the * account into onboarding without fetching the whole chat list. */ export type GetOnboardingStatusResponse = { /** * has_onboarding_chat is true once an onboarding chat exists for the account * (active or completed). The partial unique index guarantees at most one. */ hasOnboardingChat: boolean; /** * completed is true when the onboarding chat reached its Definition of Done * (onboarding_completed_at is set). Always false when has_onboarding_chat is * false. */ completed: boolean; }; /** * CompleteOnboardingHandoffRequest performs the transition from a completed * onboarding chat to a fresh regular chat. The referenced chat must be the * account's onboarding chat with onboarding_completed_at already set by the * agent's complete_onboarding tool call. Idempotent: subsequent calls return * the same regular chat that the first call created. */ export type CompleteOnboardingHandoffRequest = { chatId: number; application: string; }; /** * CompleteOnboardingHandoffResponse returns the regular chat that should * host the rest of the conversation. The chat carries the handoff summary * in its system context — frontend can navigate to it directly. */ export type CompleteOnboardingHandoffResponse = { chat: Chat; }; /** * CreateRealtimeSessionRequest mints an ephemeral realtime voice secret * (OpenAI Realtime over WebRTC or Gemini Live over WebSocket). The session * configuration — instructions, memory, chat history, tools — is fixed * server-side; the frontend only receives the short-lived secret. */ export type CreateRealtimeSessionRequest = { application: string; /** * provider selects the realtime backend: "gemini" (default) or "openai". * Unknown values are rejected; a provider not enabled in config returns * Unimplemented. */ provider: string; /** * chat_id, when non-zero, injects the recent history of this chat into the * voice instructions so the call continues the text conversation instead of * starting cold. The chat must belong to the caller. */ chatId: number; }; /** * AppendVoiceTranscriptRequest persists one completed voice turn into the * chat and mirrors it to Mattermost with a voice marker. The browser sends a * turn as soon as its transcription completes, so a dropped call loses at * most the in-flight turn. */ export type AppendVoiceTranscriptRequest = { application: string; /** chat the voice call is attached to */ chatId: number; /** ROLE_USER or ROLE_ASSISTANT */ role: Role; /** final transcript of the turn */ text: string; /** "openai" | "gemini" — for the Mattermost voice marker */ provider: string; /** * Mattermost thread root of the current voice exchange: pass the value * returned by the previous user turn so assistant replies thread under it. */ mattermostRootId: string; }; /** * AppendVoiceTranscriptResponse returns the Mattermost thread root: user turns * create it, assistant turns echo the request value back. */ export type AppendVoiceTranscriptResponse = { mattermostRootId: string; }; /** * CallVoiceToolRequest relays one tool call of a Gemini voice session. The * Live API has no remote-MCP support, so the browser forwards the model's * toolCall messages here; the allowlist is re-checked server-side and the call * is proxied to the owning upstream MCP server. */ export type CallVoiceToolRequest = { application: string; /** tool name exactly as declared to the model */ name: string; /** JSON-encoded tool arguments from the model */ arguments: string; }; /** * CallVoiceToolResponse carries the raw MCP tool result as JSON; the browser * passes it back to the model verbatim. */ export type CallVoiceToolResponse = { /** JSON-encoded MCP toolCallResult */ result: string; }; /** * VoiceTool is a tool declaration for a voice session. Declarations are * built server-side from the realtime allowlist; the browser registers them * with the provider SDK and relays execution through CallVoiceTool. */ export type VoiceTool = { name: string; description: string; /** JSON-encoded input schema */ parametersJsonSchema: string; }; /** * CreateRealtimeSessionResponse carries the ephemeral secret. The secret is * single-session and expires within minutes — the frontend must request a * fresh one for every call and never persist it. */ export type CreateRealtimeSessionResponse = { /** OpenAI: "ek_..." / Gemini: "auth_tokens/..." */ clientSecret: string; /** realtime model the session is pinned to */ model: string; /** secret expiration */ expiresAt: Date; /** provider the secret belongs to */ provider: string; /** * Tool declarations the browser must register with the provider SDK * (execution relays through CallVoiceTool). Empty for Gemini: its * declarations are pinned inside the ephemeral token's constraints. */ tools: VoiceTool[]; }; /** A gated tool call awaiting a human decision; field names match the ai_assistant_chat_proposals columns. */ export type Proposal = { id: number; chatId: number; /** tool_request row the call came from; the card groups by it */ messageId: number; toolCallId: string; /** canonical tool name */ toolCallName: string; /** JSON object */ toolCallArgs: string; title: string; reason: string; /** na | preparing | ready | failed */ audienceCalculationStatus: string; /** meaningful only when audience_calculation_status == ready */ audienceSize: number; /** preparing | pending | accepted | executing | executed | failed | rejected | expired */ status: string; expiresAt: Date; /** who chatted */ userId: number; userEmail: string; /** 0 means nobody decided yet: the column is NULL, flattened here */ decidedBy: number; decidedByEmail: string; decidedAt: Date; /** truncated tool result after execution */ toolCallResult: string; /** id / code the upstream returned */ toolCallResultRef: string; /** claude | gemini | chatgpt | llm | runner */ inferencePath: string; createdAt: Date; }; export type ListProposalsRequest = { application: string; /** 0 = every chat of the application */ chatId: number; /** empty = any status */ status: string; }; export type ListProposalsResponse = { proposals: Proposal[]; }; export type CountPendingProposalsRequest = { /** routing only: the count is per account */ application: string; }; export type CountPendingProposalsResponse = { count: number; }; export type ProposalDecision = { proposalId: number; accept: boolean; }; /** Decides a whole card at once: every pending proposal of that tool_request row not listed here is rejected. */ export type DecideProposalsRequest = { application: string; chatId: number; decisions: ProposalDecision[]; }; export type AssistantService_CreateChat = RpcMethod; export type AssistantService_GetChat = RpcMethod; export type AssistantService_GetAllChats = RpcMethod; export type AssistantService_UpdateChatTitle = RpcMethod; export type AssistantService_DeleteChat = RpcMethod; export type AssistantService_ClearChat = RpcMethod; export type AssistantService_CancelTurn = RpcMethod; export type AssistantService_ListProposals = RpcMethod; export type AssistantService_CountPendingProposals = RpcMethod; export type AssistantService_GetConsent = RpcMethod; export type AssistantService_GiveConsent = RpcMethod; export type AssistantService_CompactifyContext = RpcMethod; export type AssistantService_CheckUsageAllowed = RpcMethod; export type AssistantService_GetOnboardingStatus = RpcMethod; export type AssistantService_CompleteOnboardingHandoff = RpcMethod; export type AssistantService_CreateRealtimeSession = RpcMethod; export type AssistantService_CallVoiceTool = RpcMethod; export type AssistantService_AppendVoiceTranscript = RpcMethod; export interface AssistantService { /** Create a new chat */ CreateChat: AssistantService_CreateChat; /** Get a specific chat by ID */ GetChat: AssistantService_GetChat; /** Get all chats sorted by updated_at descending */ GetAllChats: AssistantService_GetAllChats; /** Update a chat's title */ UpdateChatTitle: AssistantService_UpdateChatTitle; /** Delete a chat */ DeleteChat: AssistantService_DeleteChat; /** Clear all messages from a chat */ ClearChat: AssistantService_ClearChat; /** * Stop the turn running in a chat. A turn outlives its stream for accounts on the * detached-turns flag, so closing the stream no longer stops it. */ CancelTurn: AssistantService_CancelTurn; /** * Follow a turn still answering; ends with the turn's own status. NotFound: its events expired, * Aborted: its pod died, FailedPrecondition: too long to follow live — reload the chat. */ AttachTurn(request: AttachTurnRequest, options?: { signal?: AbortSignal; }): Promise>; /** Stream AI inference responses */ StreamInference(request: InferenceRequest, options?: { signal?: AbortSignal; }): Promise>; StreamInferenceGemini(request: InferenceRequest, options?: { signal?: AbortSignal; }): Promise>; StreamInferenceChatGPT(request: InferenceRequest, options?: { signal?: AbortSignal; }): Promise>; /** * StreamInferenceLLM is the self-hosted LLM (Qwen) inference endpoint. It is * one of two providers that can run the onboarding agent — the frontend * routes an onboarding chat (is_onboarding=true) here or to StreamInference * (Claude) by choosing the endpoint. Every turn, including the opening one, * carries a real user message; an empty message is rejected. See is_onboarding. */ StreamInferenceLLM(request: InferenceRequest, options?: { signal?: AbortSignal; }): Promise>; ListProposals: AssistantService_ListProposals; CountPendingProposals: AssistantService_CountPendingProposals; /** Applies decisions, executes accepted calls, writes a proposal_result row and continues the turn. */ DecideProposals(request: DecideProposalsRequest, options?: { signal?: AbortSignal; }): Promise>; DecideProposalsGemini(request: DecideProposalsRequest, options?: { signal?: AbortSignal; }): Promise>; DecideProposalsLLM(request: DecideProposalsRequest, options?: { signal?: AbortSignal; }): Promise>; DecideProposalsChatGPT(request: DecideProposalsRequest, options?: { signal?: AbortSignal; }): Promise>; GetConsent: AssistantService_GetConsent; GiveConsent: AssistantService_GiveConsent; CompactifyContext: AssistantService_CompactifyContext; CheckUsageAllowed: AssistantService_CheckUsageAllowed; /** * GetOnboardingStatus reports whether the calling account already has an * onboarding chat and whether it is completed. The frontend uses it to * decide whether to route a new account into onboarding. */ GetOnboardingStatus: AssistantService_GetOnboardingStatus; /** * CompleteOnboardingHandoff atomically creates the regular chat that * succeeds a completed onboarding chat and copies the agent's handoff * summary into it. Idempotent: a second call returns the same regular * chat. Frontend calls this after detecting onboarding_completed_at on * the onboarding chat (either right after a StreamInferenceLLM turn or * on chat reload). */ CompleteOnboardingHandoff: AssistantService_CompleteOnboardingHandoff; /** * CreateRealtimeSession mints an ephemeral voice-session secret for the * configured provider (OpenAI or Gemini). Auth, usage quota and session * configuration are enforced here; provider API keys never reach the browser. */ CreateRealtimeSession: AssistantService_CreateRealtimeSession; /** * CallVoiceTool relays a Gemini voice session's tool call to the MCP proxy * upstreams, enforcing the realtime allowlist. Used only by the voice * widget; OpenAI sessions call the MCP proxy directly instead. */ CallVoiceTool: AssistantService_CallVoiceTool; /** * AppendVoiceTranscript persists one completed voice turn into the chat and * mirrors it to Mattermost (marked as voice). Called by the voice widget as * turns finish, so voice conversations are logged like text ones. */ AppendVoiceTranscript: AssistantService_AppendVoiceTranscript; }