refactor: inbox (#137)

* refactor: inbox

* fix: migrations

* fix: migrations
This commit is contained in:
Acbox Liu
2026-02-26 20:16:02 +08:00
committed by GitHub
parent d2878d841b
commit fe10abf3fc
21 changed files with 404 additions and 62 deletions
+2 -1
View File
@@ -90,6 +90,7 @@ export const MCPConnectionModel = z.union([HTTPMCPConnectionModel, SSEMCPConnect
export const InboxItemModel = z.object({
id: z.string(),
source: z.string(),
content: z.record(z.string(), z.unknown()).default({}),
header: z.record(z.string(), z.unknown()).default({}),
content: z.string().default(''),
createdAt: z.string(),
})
+4 -2
View File
@@ -399,12 +399,14 @@ CREATE TABLE IF NOT EXISTS bot_history_message_assets (
CREATE INDEX IF NOT EXISTS idx_message_assets_message_id ON bot_history_message_assets(message_id);
-- bot_inbox: per-bot message inbox for non-mentioned group messages, emails, etc.
-- bot_inbox: per-bot message inbox for channel messages, notifications, etc.
CREATE TABLE IF NOT EXISTS bot_inbox (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
source TEXT NOT NULL DEFAULT '',
content JSONB NOT NULL DEFAULT '{}'::jsonb,
header JSONB NOT NULL DEFAULT '{}'::jsonb,
content TEXT NOT NULL DEFAULT '',
action TEXT NOT NULL DEFAULT 'notify',
is_read BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
read_at TIMESTAMPTZ
@@ -0,0 +1,11 @@
-- 0018_inbox_refactor (down)
-- Revert bot_inbox to original schema: merge header+content back into content JSONB.
-- 1. Convert content back to JSONB, merging header and text.
ALTER TABLE bot_inbox ALTER COLUMN content DROP DEFAULT;
ALTER TABLE bot_inbox ALTER COLUMN content TYPE JSONB USING (COALESCE(header, '{}'::jsonb) || jsonb_build_object('text', content));
ALTER TABLE bot_inbox ALTER COLUMN content SET DEFAULT '{}'::jsonb;
-- 2. Drop added columns.
ALTER TABLE bot_inbox DROP COLUMN IF EXISTS action;
ALTER TABLE bot_inbox DROP COLUMN IF EXISTS header;
+27
View File
@@ -0,0 +1,27 @@
-- 0018_inbox_refactor
-- Refactor bot_inbox: split content JSONB into content TEXT + header JSONB, add action column.
-- 1. Add new columns (idempotent).
ALTER TABLE bot_inbox ADD COLUMN IF NOT EXISTS header JSONB NOT NULL DEFAULT '{}'::jsonb;
ALTER TABLE bot_inbox ADD COLUMN IF NOT EXISTS action TEXT NOT NULL DEFAULT 'notify';
-- 2. Migrate data and convert column type.
-- Only needed when content is still JSONB (upgrade path).
-- On a fresh DB (0001_init already defines content as TEXT), this is a no-op.
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'bot_inbox' AND column_name = 'content' AND data_type = 'jsonb'
) THEN
UPDATE bot_inbox
SET header = content - 'text',
action = 'notify'
WHERE content IS NOT NULL AND content::text <> '{}';
ALTER TABLE bot_inbox ALTER COLUMN content DROP DEFAULT;
ALTER TABLE bot_inbox ALTER COLUMN content TYPE TEXT USING COALESCE(content ->> 'text', '');
ALTER TABLE bot_inbox ALTER COLUMN content SET DEFAULT '';
END IF;
END
$$;
+3 -3
View File
@@ -1,6 +1,6 @@
-- name: CreateInboxItem :one
INSERT INTO bot_inbox (bot_id, source, content)
VALUES (sqlc.arg(bot_id), sqlc.arg(source), sqlc.arg(content))
INSERT INTO bot_inbox (bot_id, source, header, content, action)
VALUES (sqlc.arg(bot_id), sqlc.arg(source), sqlc.arg(header), sqlc.arg(content), sqlc.arg(action))
RETURNING *;
-- name: GetInboxItemByID :one
@@ -35,7 +35,7 @@ WHERE bot_id = sqlc.arg(bot_id)
-- name: SearchInboxItems :many
SELECT * FROM bot_inbox
WHERE bot_id = sqlc.arg(bot_id)
AND content::text ILIKE '%' || sqlc.arg(query) || '%'
AND content ILIKE '%' || sqlc.arg(query) || '%'
AND (sqlc.narg(start_time)::timestamptz IS NULL OR created_at >= sqlc.narg(start_time)::timestamptz)
AND (sqlc.narg(end_time)::timestamptz IS NULL OR created_at <= sqlc.narg(end_time)::timestamptz)
AND (sqlc.narg(include_read)::boolean IS NULL OR sqlc.narg(include_read)::boolean = TRUE OR is_read = FALSE)
+39 -12
View File
@@ -211,7 +211,16 @@ func (p *ChannelInboundProcessor) HandleInbound(ctx context.Context, cfg channel
if activeChatID == "" {
activeChatID = strings.TrimSpace(resolved.ChatID)
}
if !shouldTriggerAssistantResponse(msg) && !identity.ForceReply {
// Determine inbox action: trigger (immediate response) or notify (passive).
inboxAction := inbox.ActionNotify
if shouldTriggerAssistantResponse(msg) || identity.ForceReply {
inboxAction = inbox.ActionTrigger
}
// All messages go through inbox first.
inboxItem := p.createInboxItem(ctx, identity, msg, text, attachments, resolved.RouteID, inboxAction)
if inboxAction != inbox.ActionTrigger {
if p.logger != nil {
p.logger.Info(
"inbound not triggering assistant (group trigger condition not met)",
@@ -228,9 +237,14 @@ func (p *ChannelInboundProcessor) HandleInbound(ctx context.Context, cfg channel
if !strings.EqualFold(identity.BotType, "personal") {
p.persistInboundUser(ctx, resolved.RouteID, identity, msg, text, attachments, "passive_sync")
}
p.createInboxItem(ctx, identity, msg, text, attachments, resolved.RouteID)
return nil
}
// Mark the trigger inbox item as read immediately.
if inboxItem.ID != "" {
p.markInboxItemRead(ctx, inboxItem)
}
userMessagePersisted := p.persistInboundUser(ctx, resolved.RouteID, identity, msg, text, attachments, "active_chat")
// Issue chat token for reply routing.
@@ -717,17 +731,18 @@ func (p *ChannelInboundProcessor) createInboxItem(
text string,
attachments []conversation.ChatAttachment,
routeID string,
) {
action string,
) inbox.Item {
if p.inboxService == nil {
return
return inbox.Item{}
}
botID := strings.TrimSpace(ident.BotID)
if botID == "" {
return
return inbox.Item{}
}
trimmedText := strings.TrimSpace(text)
if trimmedText == "" && len(attachments) == 0 {
return
return inbox.Item{}
}
displayName := strings.TrimSpace(ident.DisplayName)
if displayName == "" {
@@ -750,17 +765,29 @@ func (p *ChannelInboundProcessor) createInboxItem(
strings.TrimSpace(msg.Conversation.Name),
attachmentPaths,
)
content := meta.ToMap()
content["text"] = trimmedText
content["route_id"] = strings.TrimSpace(routeID)
header := meta.ToMap()
header["route_id"] = strings.TrimSpace(routeID)
if _, err := p.inboxService.Create(ctx, inbox.CreateRequest{
item, err := p.inboxService.Create(ctx, inbox.CreateRequest{
BotID: botID,
Source: msg.Channel.String(),
Content: content,
}); err != nil && p.logger != nil {
Header: header,
Content: trimmedText,
Action: action,
})
if err != nil && p.logger != nil {
p.logger.Warn("create inbox item failed", slog.Any("error", err), slog.String("bot_id", botID))
}
return item
}
func (p *ChannelInboundProcessor) markInboxItemRead(ctx context.Context, item inbox.Item) {
if p.inboxService == nil || item.ID == "" {
return
}
if err := p.inboxService.MarkRead(ctx, item.BotID, []string{item.ID}); err != nil && p.logger != nil {
p.logger.Warn("mark inbox item read failed", slog.Any("error", err), slog.String("item_id", item.ID))
}
}
func buildChannelMessage(output conversation.AssistantOutput, capabilities channel.ChannelCapabilities) channel.Message {
+3 -1
View File
@@ -175,7 +175,8 @@ type gatewaySkill struct {
type gatewayInboxItem struct {
ID string `json:"id"`
Source string `json:"source"`
Content map[string]any `json:"content"`
Header map[string]any `json:"header"`
Content string `json:"content"`
CreatedAt string `json:"createdAt"`
}
@@ -408,6 +409,7 @@ func (r *Resolver) resolve(ctx context.Context, req conversation.ChatRequest) (r
inboxGatewayItems = append(inboxGatewayItems, gatewayInboxItem{
ID: item.ID,
Source: item.Source,
Header: item.Header,
Content: item.Content,
CreatedAt: item.CreatedAt.Format(time.RFC3339),
})
+28 -10
View File
@@ -37,25 +37,35 @@ func (q *Queries) CountUnreadInboxItems(ctx context.Context, botID pgtype.UUID)
}
const createInboxItem = `-- name: CreateInboxItem :one
INSERT INTO bot_inbox (bot_id, source, content)
VALUES ($1, $2, $3)
RETURNING id, bot_id, source, content, is_read, created_at, read_at
INSERT INTO bot_inbox (bot_id, source, header, content, action)
VALUES ($1, $2, $3, $4, $5)
RETURNING id, bot_id, source, header, content, action, is_read, created_at, read_at
`
type CreateInboxItemParams struct {
BotID pgtype.UUID `json:"bot_id"`
Source string `json:"source"`
Content []byte `json:"content"`
Header []byte `json:"header"`
Content string `json:"content"`
Action string `json:"action"`
}
func (q *Queries) CreateInboxItem(ctx context.Context, arg CreateInboxItemParams) (BotInbox, error) {
row := q.db.QueryRow(ctx, createInboxItem, arg.BotID, arg.Source, arg.Content)
row := q.db.QueryRow(ctx, createInboxItem,
arg.BotID,
arg.Source,
arg.Header,
arg.Content,
arg.Action,
)
var i BotInbox
err := row.Scan(
&i.ID,
&i.BotID,
&i.Source,
&i.Header,
&i.Content,
&i.Action,
&i.IsRead,
&i.CreatedAt,
&i.ReadAt,
@@ -90,7 +100,7 @@ func (q *Queries) DeleteInboxItemsByBot(ctx context.Context, botID pgtype.UUID)
}
const getInboxItemByID = `-- name: GetInboxItemByID :one
SELECT id, bot_id, source, content, is_read, created_at, read_at FROM bot_inbox
SELECT id, bot_id, source, header, content, action, is_read, created_at, read_at FROM bot_inbox
WHERE id = $1
AND bot_id = $2
`
@@ -107,7 +117,9 @@ func (q *Queries) GetInboxItemByID(ctx context.Context, arg GetInboxItemByIDPara
&i.ID,
&i.BotID,
&i.Source,
&i.Header,
&i.Content,
&i.Action,
&i.IsRead,
&i.CreatedAt,
&i.ReadAt,
@@ -116,7 +128,7 @@ func (q *Queries) GetInboxItemByID(ctx context.Context, arg GetInboxItemByIDPara
}
const listInboxItems = `-- name: ListInboxItems :many
SELECT id, bot_id, source, content, is_read, created_at, read_at FROM bot_inbox
SELECT id, bot_id, source, header, content, action, is_read, created_at, read_at FROM bot_inbox
WHERE bot_id = $1
AND ($2::boolean IS NULL OR is_read = $2::boolean)
AND ($3::text IS NULL OR source = $3::text)
@@ -152,7 +164,9 @@ func (q *Queries) ListInboxItems(ctx context.Context, arg ListInboxItemsParams)
&i.ID,
&i.BotID,
&i.Source,
&i.Header,
&i.Content,
&i.Action,
&i.IsRead,
&i.CreatedAt,
&i.ReadAt,
@@ -168,7 +182,7 @@ func (q *Queries) ListInboxItems(ctx context.Context, arg ListInboxItemsParams)
}
const listUnreadInboxItems = `-- name: ListUnreadInboxItems :many
SELECT id, bot_id, source, content, is_read, created_at, read_at FROM bot_inbox
SELECT id, bot_id, source, header, content, action, is_read, created_at, read_at FROM bot_inbox
WHERE bot_id = $1
AND is_read = FALSE
ORDER BY created_at ASC
@@ -193,7 +207,9 @@ func (q *Queries) ListUnreadInboxItems(ctx context.Context, arg ListUnreadInboxI
&i.ID,
&i.BotID,
&i.Source,
&i.Header,
&i.Content,
&i.Action,
&i.IsRead,
&i.CreatedAt,
&i.ReadAt,
@@ -228,9 +244,9 @@ func (q *Queries) MarkInboxItemsRead(ctx context.Context, arg MarkInboxItemsRead
}
const searchInboxItems = `-- name: SearchInboxItems :many
SELECT id, bot_id, source, content, is_read, created_at, read_at FROM bot_inbox
SELECT id, bot_id, source, header, content, action, is_read, created_at, read_at FROM bot_inbox
WHERE bot_id = $1
AND content::text ILIKE '%' || $2 || '%'
AND content ILIKE '%' || $2 || '%'
AND ($3::timestamptz IS NULL OR created_at >= $3::timestamptz)
AND ($4::timestamptz IS NULL OR created_at <= $4::timestamptz)
AND ($5::boolean IS NULL OR $5::boolean = TRUE OR is_read = FALSE)
@@ -267,7 +283,9 @@ func (q *Queries) SearchInboxItems(ctx context.Context, arg SearchInboxItemsPara
&i.ID,
&i.BotID,
&i.Source,
&i.Header,
&i.Content,
&i.Action,
&i.IsRead,
&i.CreatedAt,
&i.ReadAt,
+3 -1
View File
@@ -105,7 +105,9 @@ type BotInbox struct {
ID pgtype.UUID `json:"id"`
BotID pgtype.UUID `json:"bot_id"`
Source string `json:"source"`
Content []byte `json:"content"`
Header []byte `json:"header"`
Content string `json:"content"`
Action string `json:"action"`
IsRead bool `json:"is_read"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
ReadAt pgtype.Timestamptz `json:"read_at"`
+1 -1
View File
@@ -142,7 +142,7 @@ func (h *InboxHandler) Create(c echo.Context) error {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
req.BotID = botID
if len(req.Content) == 0 {
if strings.TrimSpace(req.Content) == "" {
return echo.NewHTTPError(http.StatusBadRequest, "content is required")
}
item, err := h.service.Create(c.Request().Context(), req)
+27 -10
View File
@@ -28,11 +28,18 @@ func NewService(log *slog.Logger, queries *sqlc.Queries) *Service {
}
}
const (
ActionTrigger = "trigger"
ActionNotify = "notify"
)
type Item struct {
ID string `json:"id"`
BotID string `json:"bot_id"`
Source string `json:"source"`
Content map[string]any `json:"content"`
Header map[string]any `json:"header"`
Content string `json:"content"`
Action string `json:"action"`
IsRead bool `json:"is_read"`
CreatedAt time.Time `json:"created_at"`
ReadAt time.Time `json:"read_at,omitempty"`
@@ -41,7 +48,9 @@ type Item struct {
type CreateRequest struct {
BotID string `json:"bot_id"`
Source string `json:"source"`
Content map[string]any `json:"content"`
Header map[string]any `json:"header"`
Content string `json:"content"`
Action string `json:"action"`
}
type ListFilter struct {
@@ -69,15 +78,21 @@ func (s *Service) Create(ctx context.Context, req CreateRequest) (Item, error) {
if err != nil {
return Item{}, err
}
content, err := json.Marshal(req.Content)
header, err := json.Marshal(req.Header)
if err != nil {
return Item{}, err
}
action := req.Action
if action != ActionTrigger && action != ActionNotify {
action = ActionNotify
}
row, err := s.queries.CreateInboxItem(ctx, sqlc.CreateInboxItemParams{
BotID: botUUID,
Source: req.Source,
Content: content,
Header: header,
Content: req.Content,
Action: action,
})
if err != nil {
return Item{}, err
@@ -236,18 +251,20 @@ func (s *Service) Delete(ctx context.Context, botID, itemID string) error {
// --- conversion helpers ---
func rowToItem(row sqlc.BotInbox) Item {
var content map[string]any
if len(row.Content) > 0 {
_ = json.Unmarshal(row.Content, &content)
var header map[string]any
if len(row.Header) > 0 {
_ = json.Unmarshal(row.Header, &header)
}
if content == nil {
content = map[string]any{}
if header == nil {
header = map[string]any{}
}
return Item{
ID: pgUUIDToString(row.ID),
BotID: pgUUIDToString(row.BotID),
Source: row.Source,
Content: content,
Header: header,
Content: row.Content,
Action: row.Action,
IsRead: row.IsRead,
CreatedAt: db.TimeFromPg(row.CreatedAt),
ReadAt: db.TimeFromPg(row.ReadAt),
+1
View File
@@ -137,6 +137,7 @@ func (e *Executor) CallTool(ctx context.Context, session mcpgw.ToolSessionContex
entry := map[string]any{
"id": item.ID,
"source": item.Source,
"header": item.Header,
"content": item.Content,
"is_read": item.IsRead,
"created_at": item.CreatedAt.Format(time.RFC3339),
+8 -1
View File
@@ -29,13 +29,20 @@ ${skill.content}
const formatInbox = (items: InboxItem[]): string => {
if (!items || items.length === 0) return ''
const formatted = items.map((item) => ({
id: item.id,
source: item.source,
header: item.header,
content: item.content,
createdAt: item.createdAt,
}))
return `
## Inbox (${items.length} unread)
These are messages from other channels NOT from the current conversation. Use ${quote('send')} or ${quote('react')} if you want to respond to any of them.
<inbox>
${JSON.stringify(items)}
${JSON.stringify(formatted)}
</inbox>
Use ${quote('search_inbox')} to find older messages by keyword.
+2 -1
View File
@@ -33,7 +33,8 @@ export const allActions = Object.values(AgentAction)
export interface InboxItem {
id: string
source: string
content: Record<string, unknown>
header: Record<string, unknown>
content: string
createdAt: string
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+43 -4
View File
@@ -499,6 +499,12 @@ export type HandlersPingResponse = {
status?: string;
};
export type HandlersRefreshResponse = {
access_token?: string;
expires_at?: string;
token_type?: string;
};
export type HandlersSkillItem = {
content?: string;
description?: string;
@@ -623,19 +629,23 @@ export type InboxCountResult = {
};
export type InboxCreateRequest = {
action?: string;
bot_id?: string;
content?: {
content?: string;
header?: {
[key: string]: unknown;
};
source?: string;
};
export type InboxItem = {
action?: string;
bot_id?: string;
content?: {
content?: string;
created_at?: string;
header?: {
[key: string]: unknown;
};
created_at?: string;
id?: string;
is_read?: boolean;
read_at?: string;
@@ -975,7 +985,7 @@ export type SearchprovidersProviderMeta = {
provider?: string;
};
export type SearchprovidersProviderName = 'brave' | 'bing' | 'google';
export type SearchprovidersProviderName = 'brave' | 'bing' | 'google' | 'tavily';
export type SearchprovidersUpdateRequest = {
config?: {
@@ -1130,6 +1140,35 @@ export type PostAuthLoginResponses = {
export type PostAuthLoginResponse = PostAuthLoginResponses[keyof PostAuthLoginResponses];
export type PostAuthRefreshData = {
body?: never;
path?: never;
query?: never;
url: '/auth/refresh';
};
export type PostAuthRefreshErrors = {
/**
* Unauthorized
*/
401: HandlersErrorResponse;
/**
* Internal Server Error
*/
500: HandlersErrorResponse;
};
export type PostAuthRefreshError = PostAuthRefreshErrors[keyof PostAuthRefreshErrors];
export type PostAuthRefreshResponses = {
/**
* OK
*/
200: HandlersRefreshResponse;
};
export type PostAuthRefreshResponse = PostAuthRefreshResponses[keyof PostAuthRefreshResponses];
export type GetBotsData = {
body?: never;
path?: never;
+66 -4
View File
@@ -61,6 +61,40 @@ const docTemplate = `{
}
}
},
"/auth/refresh": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "Issue a new JWT using the existing claims with updated expiration",
"tags": [
"auth"
],
"summary": "Refresh Token",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handlers.RefreshResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/bots": {
"get": {
"description": "List bots accessible to current user (admin can specify owner_id)",
@@ -7213,6 +7247,20 @@ const docTemplate = `{
}
}
},
"handlers.RefreshResponse": {
"type": "object",
"properties": {
"access_token": {
"type": "string"
},
"expires_at": {
"type": "string"
},
"token_type": {
"type": "string"
}
}
},
"handlers.SkillItem": {
"type": "object",
"properties": {
@@ -7514,10 +7562,16 @@ const docTemplate = `{
"inbox.CreateRequest": {
"type": "object",
"properties": {
"action": {
"type": "string"
},
"bot_id": {
"type": "string"
},
"content": {
"type": "string"
},
"header": {
"type": "object",
"additionalProperties": {}
},
@@ -7529,16 +7583,22 @@ const docTemplate = `{
"inbox.Item": {
"type": "object",
"properties": {
"action": {
"type": "string"
},
"bot_id": {
"type": "string"
},
"content": {
"type": "object",
"additionalProperties": {}
"type": "string"
},
"created_at": {
"type": "string"
},
"header": {
"type": "object",
"additionalProperties": {}
},
"id": {
"type": "string"
},
@@ -8381,12 +8441,14 @@ const docTemplate = `{
"enum": [
"brave",
"bing",
"google"
"google",
"tavily"
],
"x-enum-varnames": [
"ProviderBrave",
"ProviderBing",
"ProviderGoogle"
"ProviderGoogle",
"ProviderTavily"
]
},
"searchproviders.UpdateRequest": {
+66 -4
View File
@@ -52,6 +52,40 @@
}
}
},
"/auth/refresh": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "Issue a new JWT using the existing claims with updated expiration",
"tags": [
"auth"
],
"summary": "Refresh Token",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handlers.RefreshResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/bots": {
"get": {
"description": "List bots accessible to current user (admin can specify owner_id)",
@@ -7204,6 +7238,20 @@
}
}
},
"handlers.RefreshResponse": {
"type": "object",
"properties": {
"access_token": {
"type": "string"
},
"expires_at": {
"type": "string"
},
"token_type": {
"type": "string"
}
}
},
"handlers.SkillItem": {
"type": "object",
"properties": {
@@ -7505,10 +7553,16 @@
"inbox.CreateRequest": {
"type": "object",
"properties": {
"action": {
"type": "string"
},
"bot_id": {
"type": "string"
},
"content": {
"type": "string"
},
"header": {
"type": "object",
"additionalProperties": {}
},
@@ -7520,16 +7574,22 @@
"inbox.Item": {
"type": "object",
"properties": {
"action": {
"type": "string"
},
"bot_id": {
"type": "string"
},
"content": {
"type": "object",
"additionalProperties": {}
"type": "string"
},
"created_at": {
"type": "string"
},
"header": {
"type": "object",
"additionalProperties": {}
},
"id": {
"type": "string"
},
@@ -8372,12 +8432,14 @@
"enum": [
"brave",
"bing",
"google"
"google",
"tavily"
],
"x-enum-varnames": [
"ProviderBrave",
"ProviderBing",
"ProviderGoogle"
"ProviderGoogle",
"ProviderTavily"
]
},
"searchproviders.UpdateRequest": {
+42 -2
View File
@@ -825,6 +825,15 @@ definitions:
status:
type: string
type: object
handlers.RefreshResponse:
properties:
access_token:
type: string
expires_at:
type: string
token_type:
type: string
type: object
handlers.SkillItem:
properties:
content:
@@ -1022,9 +1031,13 @@ definitions:
type: object
inbox.CreateRequest:
properties:
action:
type: string
bot_id:
type: string
content:
type: string
header:
additionalProperties: {}
type: object
source:
@@ -1032,13 +1045,17 @@ definitions:
type: object
inbox.Item:
properties:
action:
type: string
bot_id:
type: string
content:
additionalProperties: {}
type: object
type: string
created_at:
type: string
header:
additionalProperties: {}
type: object
id:
type: string
is_read:
@@ -1599,11 +1616,13 @@ definitions:
- brave
- bing
- google
- tavily
type: string
x-enum-varnames:
- ProviderBrave
- ProviderBing
- ProviderGoogle
- ProviderTavily
searchproviders.UpdateRequest:
properties:
config:
@@ -1824,6 +1843,27 @@ paths:
summary: Login
tags:
- auth
/auth/refresh:
post:
description: Issue a new JWT using the existing claims with updated expiration
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handlers.RefreshResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/handlers.ErrorResponse'
security:
- BearerAuth: []
summary: Refresh Token
tags:
- auth
/bots:
get:
description: List bots accessible to current user (admin can specify owner_id)