Files
supabase/apps/studio/lib/ai/tools/index.ts
Charis 13de8189c9 feat(studio): assistant get_active_incidents on status page endpoint (#50994)
## Summary

* Adds a second branch to the assistant's `get_active_incidents` tool
(`apps/studio/lib/ai/tools/incident-tools.ts`) that reads
`/api/status-page` when the global ConfigCat flag `incidentIoStatusPage`
is on, instead of the legacy `/api/incident-status` endpoint.
* Filters on `visible`, ignores `show_banner` (only the global banner
respects it), and concatenates ongoing incidents + in-progress
maintenances (not scheduled maintenances).
* Adds `isServerFlagEnabled` to `lib/server/configcat.ts` for reading
global, non-user-targeted ConfigCat flags server-side, and threads the
flag through `getTools` from `pages/api/ai/sql/generate-v4.ts`.

This is PR 3 of 6 in the
[FE-4057](https://linear.app/supabase/issue/FE-4057/frontend-bannerbot-reconfigured)
stack — stacked on `charis/fe-4057-pr2-support-form`. Behind the
`incidentIoStatusPage` flag (default off), so this ships no user-visible
change on its own. See Linear
[FE-4057](https://linear.app/supabase/issue/FE-4057/frontend-bannerbot-reconfigured)
for full context.

## Test plan

- [X] `pnpm --filter studio run typecheck`
- [X] `pnpm --filter studio run lint:ratchet`
- [X] `pnpm knip --workspace apps/studio`
- [X] `pnpm test:prettier`
- [X] `pnpm --filter studio exec vitest run
lib/ai/tools/incident-tools.test.ts` (19/19 passing, including 7 new
tests for the status-page branch)
2026-09-29 15:28:49 -04:00

101 lines
3.1 KiB
TypeScript

import { ToolSet } from 'ai'
import { IS_PLATFORM } from 'common'
import { filterToolsByOptInLevel } from '../tool-filter'
import { getFallbackTools } from './fallback-tools'
import { getIncidentTools } from './incident-tools'
import { getMcpTools } from './mcp-tools'
import { getNotebookTools } from './notebook-tools'
import { getReportTools } from './report-tools'
import { getSchemaTools } from './schema-tools'
import { getStudioTools } from './studio-tools'
import { getSupportLifecycleTools } from './support-tools'
import { AiOptInLevel } from '@/hooks/misc/useOrgOptedIntoAi'
export const getTools = async ({
projectRef,
connectionString,
authorization,
aiOptInLevel,
accessToken,
baseUrl,
supportMode,
isExplorerEnabled,
useStatusPageWidget,
signal,
}: {
projectRef: string
connectionString: string
authorization?: string
aiOptInLevel: AiOptInLevel
accessToken?: string
baseUrl?: string
supportMode?: boolean
// Notebooks haven't shipped yet — they live behind the Explorer feature flag, so the
// assistant must not advertise list_notebooks/get_notebook until the caller confirms the
// flag is on for this user.
isExplorerEnabled?: boolean
useStatusPageWidget?: boolean
// Required: tools fetched from the remote MCP server hold an HTTP connection
// that is closed when this signal aborts (i.e. when the request ends).
signal: AbortSignal
}) => {
// Always include studio tools
let tools: ToolSet = getStudioTools({ projectRef, connectionString, authorization, aiOptInLevel })
// If self-hosted, only add fallback tools
if (!IS_PLATFORM) {
tools = {
...tools,
...getFallbackTools({
projectRef,
connectionString,
authorization,
includeSchemaMetadata: aiOptInLevel !== 'disabled',
}),
}
} else if (accessToken) {
// If platform, fetch MCP and other platform specific tools. The MCP tools
// are fetched from the remote MCP server over the network, so a failure
// there (outage, timeout, auth) should degrade gracefully to the remaining
// tools rather than break the entire assistant.
let mcpTools: ToolSet = {}
try {
mcpTools = await getMcpTools({
accessToken,
projectRef,
aiOptInLevel,
signal,
})
} catch (error) {
console.error('Failed to fetch MCP tools:', error)
}
tools = {
...tools,
...mcpTools,
...getSchemaTools({
projectRef,
connectionString,
authorization,
}),
...getReportTools({ projectRef, authorization }),
...(isExplorerEnabled
? getNotebookTools({
projectRef,
connectionString,
authorization,
aiOptInLevel,
})
: {}),
...(baseUrl ? getIncidentTools({ baseUrl, useStatusPageWidget }) : {}),
}
}
// Filter all tools based on the (potentially modified) AI opt-in level
const toolsWithSupport = supportMode ? { ...tools, ...getSupportLifecycleTools() } : tools
const filteredTools: ToolSet = filterToolsByOptInLevel(toolsWithSupport, aiOptInLevel)
return filteredTools
}