chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
+143
View File
@@ -0,0 +1,143 @@
import {
DOWNDETECTOR_API_BASE,
type DowndetectorGetCompanyParams,
type DowndetectorGetCompanyResponse,
} from '@/tools/downdetector/types'
import {
downdetectorHeaders,
encodePathParam,
extractDowndetectorError,
} from '@/tools/downdetector/utils'
import type { ToolConfig } from '@/tools/types'
const DEFAULT_FIELDS =
'id,name,slug,url,stats_24,baseline,baseline_current,category_id,status,country_iso,site_id,indicators,description'
interface RawCompany {
id?: number
name?: string
slug?: string
url?: string
status?: string
category_id?: number
country_iso?: string
site_id?: number
baseline_current?: number
stats_24?: number[]
baseline?: number[]
indicators?: string[]
description?: string
}
export const getCompanyTool: ToolConfig<
DowndetectorGetCompanyParams,
DowndetectorGetCompanyResponse
> = {
id: 'downdetector_get_company',
name: 'Downdetector Get Company',
description:
'Get details for a Downdetector company by id, including its current status, 24h report statistics, baseline, and available problem indicators.',
version: '1.0.0',
params: {
companyId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Downdetector company id',
},
fields: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Comma-separated list of fields to return (defaults to a rich set including status, stats_24, and baseline)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Downdetector API Bearer token',
},
},
request: {
url: (params) => {
const url = new URL(
`${DOWNDETECTOR_API_BASE}/companies/${encodePathParam(params.companyId, 'Company ID')}`
)
url.searchParams.set('fields', params.fields || DEFAULT_FIELDS)
return url.toString()
},
method: 'GET',
headers: (params) => downdetectorHeaders(params.apiKey),
},
transformResponse: async (response: Response) => {
const data: RawCompany = await response.json()
if (!response.ok) {
throw new Error(extractDowndetectorError(data, 'Failed to get company'))
}
return {
success: true,
output: {
company: {
id: data.id ?? null,
name: data.name ?? null,
slug: data.slug ?? null,
url: data.url ?? null,
status: data.status ?? null,
categoryId: data.category_id ?? null,
countryIso: data.country_iso ?? null,
siteId: data.site_id ?? null,
baselineCurrent: data.baseline_current ?? null,
stats24: data.stats_24 ?? [],
baseline: data.baseline ?? [],
indicators: data.indicators ?? [],
description: data.description ?? null,
},
},
}
},
outputs: {
company: {
type: 'object',
description: 'Company details',
properties: {
id: { type: 'number', description: 'Company id' },
name: { type: 'string', description: 'Company name' },
slug: { type: 'string', description: 'Company slug' },
url: { type: 'string', description: 'Company status page URL' },
status: {
type: 'string',
description: 'Cached current status (success, warning, or danger)',
},
categoryId: { type: 'number', description: 'Category id' },
countryIso: { type: 'string', description: 'ISO-2 country code' },
siteId: { type: 'number', description: 'Site id' },
baselineCurrent: {
type: 'number',
description: 'The current considered average reports at this point in time',
},
stats24: {
type: 'array',
description: 'Reports over the last 24h in 15-minute buckets',
items: { type: 'number' },
},
baseline: {
type: 'array',
description: 'Averaged baseline values per 15m over 24h',
items: { type: 'number' },
},
indicators: {
type: 'array',
description: 'List of available problem indicators',
items: { type: 'string' },
},
description: { type: 'string', description: 'Company description' },
},
},
},
}
@@ -0,0 +1,119 @@
import {
DOWNDETECTOR_API_BASE,
type DowndetectorGetCompanyAttributionParams,
type DowndetectorGetCompanyAttributionResponse,
} from '@/tools/downdetector/types'
import {
downdetectorHeaders,
encodePathParam,
extractDowndetectorError,
} from '@/tools/downdetector/utils'
import type { ToolConfig } from '@/tools/types'
interface RawAttribution {
attribution?: number
attribution_calculated_at?: string
user_impact?: number
user_impact_calculated_at?: string
reason?: number
danger_duration_s?: number
incident_id?: number
incident_created_at?: string
}
export const getCompanyAttributionTool: ToolConfig<
DowndetectorGetCompanyAttributionParams,
DowndetectorGetCompanyAttributionResponse
> = {
id: 'downdetector_get_company_attribution',
name: 'Downdetector Get Company Attribution',
description:
'Get the incident attribution for a Downdetector company while it is in an outage state — whether the issue is internal (isolated) or external (a dependency), the estimated user impact, and the related incident. Requires Incident Attribution access.',
version: '1.0.0',
params: {
companyId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Downdetector company id',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Downdetector API Bearer token',
},
},
request: {
url: (params) =>
`${DOWNDETECTOR_API_BASE}/companies/${encodePathParam(params.companyId, 'Company ID')}/attribution`,
method: 'GET',
headers: (params) => downdetectorHeaders(params.apiKey),
},
transformResponse: async (response: Response) => {
const data: RawAttribution = await response.json()
if (!response.ok) {
throw new Error(extractDowndetectorError(data, 'Failed to get company attribution'))
}
return {
success: true,
output: {
attribution: {
attribution: data.attribution ?? null,
attributionCalculatedAt: data.attribution_calculated_at ?? null,
userImpact: data.user_impact ?? null,
userImpactCalculatedAt: data.user_impact_calculated_at ?? null,
reason: data.reason ?? null,
dangerDurationS: data.danger_duration_s ?? null,
incidentId: data.incident_id ?? null,
incidentCreatedAt: data.incident_created_at ?? null,
},
},
}
},
outputs: {
attribution: {
type: 'object',
description: 'Incident attribution detail',
properties: {
attribution: {
type: 'number',
description: 'Attribution enum (0 N/A, 1 undetermined, 2 external, 3 internal)',
},
attributionCalculatedAt: {
type: 'string',
description: 'ISO 8601 timestamp when attribution was calculated',
},
userImpact: {
type: 'number',
description: 'User impact enum (0 low, 1 medium, 2 high, 3 very high)',
},
userImpactCalculatedAt: {
type: 'string',
description: 'ISO 8601 timestamp when user impact was calculated',
},
reason: {
type: 'number',
description: 'Reason enum explaining how the attribution value was calculated (0-7)',
},
dangerDurationS: {
type: 'number',
description: 'Duration of the current danger (outage) state in seconds',
},
incidentId: {
type: 'number',
description: 'Id of the related incident (null when attribution is N/A)',
},
incidentCreatedAt: {
type: 'string',
description: 'ISO 8601 timestamp when the related incident was created',
},
},
},
},
}
@@ -0,0 +1,63 @@
import {
DOWNDETECTOR_API_BASE,
type DowndetectorGetCompanyBaselineParams,
type DowndetectorGetCompanyBaselineResponse,
} from '@/tools/downdetector/types'
import {
downdetectorHeaders,
encodePathParam,
extractDowndetectorError,
} from '@/tools/downdetector/utils'
import type { ToolConfig } from '@/tools/types'
export const getCompanyBaselineTool: ToolConfig<
DowndetectorGetCompanyBaselineParams,
DowndetectorGetCompanyBaselineResponse
> = {
id: 'downdetector_get_company_baseline',
name: 'Downdetector Get Company Baseline',
description:
'Get the current baseline report value for a Downdetector company. This is the expected average number of reports for the current period, used to judge whether current reports are abnormal.',
version: '1.0.0',
params: {
companyId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Downdetector company id',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Downdetector API Bearer token',
},
},
request: {
url: (params) =>
`${DOWNDETECTOR_API_BASE}/companies/${encodePathParam(params.companyId, 'Company ID')}/baseline/current`,
method: 'GET',
headers: (params) => downdetectorHeaders(params.apiKey),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(extractDowndetectorError(data, 'Failed to get company baseline'))
}
return {
success: true,
output: { baseline: typeof data === 'number' ? data : (data?.baseline ?? 0) },
}
},
outputs: {
baseline: {
type: 'number',
description: 'The current baseline (expected average reports) for this period',
},
},
}
@@ -0,0 +1,154 @@
import {
DOWNDETECTOR_API_BASE,
type DowndetectorGetCompanyEventsParams,
type DowndetectorGetCompanyEventsResponse,
} from '@/tools/downdetector/types'
import {
downdetectorHeaders,
downdetectorNextPageOutput,
encodePathParam,
extractDowndetectorError,
nextPageFromResponse,
} from '@/tools/downdetector/utils'
import type { ToolConfig } from '@/tools/types'
interface RawEventMeasurement {
started_on?: string
ended_on?: string
expected?: number
actual?: number
}
interface RawEvent {
id?: number
title?: string
body?: string
company_id?: number
created_at?: string
publish_at?: string
is_active?: boolean
measurement?: RawEventMeasurement
}
export const getCompanyEventsTool: ToolConfig<
DowndetectorGetCompanyEventsParams,
DowndetectorGetCompanyEventsResponse
> = {
id: 'downdetector_get_company_events',
name: 'Downdetector Get Company Events',
description:
'Get the published events (such as detected outages) for a Downdetector company, including the measured vs expected report volume for each event.',
version: '1.0.0',
params: {
companyId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Downdetector company id',
},
startdate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 8601 start of the time range (only works together with enddate)',
},
enddate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 8601 end of the time range (only works together with startdate)',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Requested page number (1-indexed)',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page, between 10 and 100',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Downdetector API Bearer token',
},
},
request: {
url: (params) => {
const url = new URL(
`${DOWNDETECTOR_API_BASE}/companies/${encodePathParam(params.companyId, 'Company ID')}/events`
)
if (params.startdate) url.searchParams.set('startdate', params.startdate)
if (params.enddate) url.searchParams.set('enddate', params.enddate)
if (params.page !== undefined) url.searchParams.set('page', String(params.page))
if (params.pageSize !== undefined) url.searchParams.set('page_size', String(params.pageSize))
return url.toString()
},
method: 'GET',
headers: (params) => downdetectorHeaders(params.apiKey),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(extractDowndetectorError(data, 'Failed to get company events'))
}
const rows: RawEvent[] = Array.isArray(data) ? data : []
const events = rows.map((event) => ({
id: event.id ?? null,
title: event.title ?? null,
body: event.body ?? null,
companyId: event.company_id ?? null,
createdAt: event.created_at ?? null,
publishAt: event.publish_at ?? null,
isActive: event.is_active ?? null,
measurement: event.measurement
? {
startedOn: event.measurement.started_on ?? null,
endedOn: event.measurement.ended_on ?? null,
expected: event.measurement.expected ?? null,
actual: event.measurement.actual ?? null,
}
: null,
}))
return { success: true, output: { events, nextPage: nextPageFromResponse(response) } }
},
outputs: {
events: {
type: 'array',
description: 'List of events for the company',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Event id' },
title: { type: 'string', description: 'Localized event title' },
body: { type: 'string', description: 'Localized event body' },
companyId: { type: 'number', description: 'Id of the impacted company' },
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
publishAt: { type: 'string', description: 'ISO 8601 publish timestamp' },
isActive: { type: 'boolean', description: 'Whether the event is ongoing' },
measurement: {
type: 'object',
description: 'Measured vs expected report volume for the event window',
properties: {
startedOn: { type: 'string', description: 'Measurement window start (ISO 8601)' },
endedOn: { type: 'string', description: 'Measurement window end (ISO 8601)' },
expected: { type: 'number', description: 'Expected reports based on historic data' },
actual: { type: 'number', description: 'Actual reports in the window' },
},
},
},
},
},
nextPage: downdetectorNextPageOutput,
},
}
@@ -0,0 +1,109 @@
import {
DOWNDETECTOR_API_BASE,
type DowndetectorGetCompanyIncidentsParams,
type DowndetectorIncidentsResponse,
} from '@/tools/downdetector/types'
import {
downdetectorHeaders,
downdetectorIncidentItemSchema,
downdetectorNextPageOutput,
encodePathParam,
extractDowndetectorError,
mapDowndetectorIncident,
nextPageFromResponse,
} from '@/tools/downdetector/utils'
import type { ToolConfig } from '@/tools/types'
export const getCompanyIncidentsTool: ToolConfig<
DowndetectorGetCompanyIncidentsParams,
DowndetectorIncidentsResponse
> = {
id: 'downdetector_get_company_incidents',
name: 'Downdetector Get Company Incidents',
description:
'Get the list of incidents (outages) for a Downdetector company. Defaults to the last 24 hours unless a date range is provided.',
version: '1.0.0',
params: {
companyId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Downdetector company id',
},
onlyActive: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'When true, only the currently active incident is returned',
},
startdate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 8601 start of the time range (only works together with enddate)',
},
enddate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 8601 end of the time range (only works together with startdate)',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Requested page number (1-indexed)',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page, between 10 and 100',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Downdetector API Bearer token',
},
},
request: {
url: (params) => {
const url = new URL(
`${DOWNDETECTOR_API_BASE}/companies/${encodePathParam(params.companyId, 'Company ID')}/incidents`
)
if (params.onlyActive !== undefined)
url.searchParams.set('only_active', String(params.onlyActive))
if (params.startdate) url.searchParams.set('startdate', params.startdate)
if (params.enddate) url.searchParams.set('enddate', params.enddate)
if (params.page !== undefined) url.searchParams.set('page', String(params.page))
if (params.pageSize !== undefined) url.searchParams.set('page_size', String(params.pageSize))
return url.toString()
},
method: 'GET',
headers: (params) => downdetectorHeaders(params.apiKey),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(extractDowndetectorError(data, 'Failed to get company incidents'))
}
const rows = Array.isArray(data) ? data : []
const incidents = rows.map(mapDowndetectorIncident)
return { success: true, output: { incidents, nextPage: nextPageFromResponse(response) } }
},
outputs: {
incidents: {
type: 'array',
description: 'List of incidents for the company',
items: downdetectorIncidentItemSchema,
},
nextPage: downdetectorNextPageOutput,
},
}
@@ -0,0 +1,105 @@
import {
DOWNDETECTOR_API_BASE,
type DowndetectorGetCompanyIndicatorsParams,
type DowndetectorGetCompanyIndicatorsResponse,
} from '@/tools/downdetector/types'
import {
downdetectorHeaders,
encodePathParam,
extractDowndetectorError,
} from '@/tools/downdetector/utils'
import type { ToolConfig } from '@/tools/types'
interface RawIndicator {
slug?: string
indicator?: string
key?: string
amount?: number
percentage?: number
}
export const getCompanyIndicatorsTool: ToolConfig<
DowndetectorGetCompanyIndicatorsParams,
DowndetectorGetCompanyIndicatorsResponse
> = {
id: 'downdetector_get_company_indicators',
name: 'Downdetector Get Company Indicators',
description:
'Get the problem indicators (e.g. "App crashing", "Login", "Server connection") reported for a Downdetector company over a time period, with the report counts and percentages for each.',
version: '1.0.0',
params: {
companyId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Downdetector company id',
},
startdate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 8601 start of the time range (only works together with enddate)',
},
enddate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 8601 end of the time range (only works together with startdate)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Downdetector API Bearer token',
},
},
request: {
url: (params) => {
const url = new URL(
`${DOWNDETECTOR_API_BASE}/companies/${encodePathParam(params.companyId, 'Company ID')}/indicators`
)
if (params.startdate) url.searchParams.set('startdate', params.startdate)
if (params.enddate) url.searchParams.set('enddate', params.enddate)
return url.toString()
},
method: 'GET',
headers: (params) => downdetectorHeaders(params.apiKey),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(extractDowndetectorError(data, 'Failed to get company indicators'))
}
const rows: RawIndicator[] = Array.isArray(data) ? data : []
const indicators = rows.map((item) => ({
slug: item.slug ?? null,
indicator: item.indicator ?? null,
key: item.key ?? null,
amount: item.amount ?? null,
percentage: item.percentage ?? null,
}))
return { success: true, output: { indicators } }
},
outputs: {
indicators: {
type: 'array',
description: 'Reported problem indicators with their counts',
items: {
type: 'object',
properties: {
slug: { type: 'string', description: 'Indicator slug' },
indicator: { type: 'string', description: 'Human-readable indicator label' },
key: { type: 'string', description: 'Indicator key' },
amount: { type: 'number', description: 'Number of reports for this indicator' },
percentage: { type: 'number', description: 'Share of total reports (percentage)' },
},
},
},
},
}
@@ -0,0 +1,63 @@
import {
DOWNDETECTOR_API_BASE,
type DowndetectorGetCompanyLast15Params,
type DowndetectorGetCompanyLast15Response,
} from '@/tools/downdetector/types'
import {
downdetectorHeaders,
encodePathParam,
extractDowndetectorError,
} from '@/tools/downdetector/utils'
import type { ToolConfig } from '@/tools/types'
export const getCompanyLast15Tool: ToolConfig<
DowndetectorGetCompanyLast15Params,
DowndetectorGetCompanyLast15Response
> = {
id: 'downdetector_get_company_last_15',
name: 'Downdetector Get Company Last 15 Minutes',
description:
'Get the number of outage reports for a Downdetector company over the last 15 minutes. A convenient near-real-time signal for threshold-based alerting.',
version: '1.0.0',
params: {
companyId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Downdetector company id',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Downdetector API Bearer token',
},
},
request: {
url: (params) =>
`${DOWNDETECTOR_API_BASE}/companies/${encodePathParam(params.companyId, 'Company ID')}/last_15`,
method: 'GET',
headers: (params) => downdetectorHeaders(params.apiKey),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(extractDowndetectorError(data, 'Failed to get last 15 minutes of reports'))
}
return {
success: true,
output: { count: typeof data === 'number' ? data : (data?.count ?? 0) },
}
},
outputs: {
count: {
type: 'number',
description: 'Number of reports over the last 15 minutes',
},
},
}
@@ -0,0 +1,76 @@
import {
DOWNDETECTOR_API_BASE,
type DowndetectorGetCompanyStatusParams,
type DowndetectorGetCompanyStatusResponse,
} from '@/tools/downdetector/types'
import {
downdetectorHeaders,
encodePathParam,
extractDowndetectorError,
} from '@/tools/downdetector/utils'
import type { ToolConfig } from '@/tools/types'
export const getCompanyStatusTool: ToolConfig<
DowndetectorGetCompanyStatusParams,
DowndetectorGetCompanyStatusResponse
> = {
id: 'downdetector_get_company_status',
name: 'Downdetector Get Company Status',
description:
'Get the current detected status for a Downdetector company. Returns "success" (no problems), "warning", or "danger" (likely outage).',
version: '1.0.0',
params: {
companyId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Downdetector company id',
},
threshold: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description:
'If set, returns "danger" when the current report count is above this threshold, otherwise "success"',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Downdetector API Bearer token',
},
},
request: {
url: (params) => {
const url = new URL(
`${DOWNDETECTOR_API_BASE}/companies/${encodePathParam(params.companyId, 'Company ID')}/status`
)
if (params.threshold !== undefined)
url.searchParams.set('threshold', String(params.threshold))
return url.toString()
},
method: 'GET',
headers: (params) => downdetectorHeaders(params.apiKey),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(extractDowndetectorError(data, 'Failed to get company status'))
}
return {
success: true,
output: { status: typeof data === 'string' ? data : (data?.status ?? '') },
}
},
outputs: {
status: {
type: 'string',
description: 'Current status: "success", "warning", or "danger"',
},
},
}
@@ -0,0 +1,80 @@
import {
DOWNDETECTOR_API_BASE,
type DowndetectorGetProviderParams,
type DowndetectorGetProviderResponse,
} from '@/tools/downdetector/types'
import {
downdetectorHeaders,
encodePathParam,
extractDowndetectorError,
} from '@/tools/downdetector/utils'
import type { ToolConfig } from '@/tools/types'
interface RawProvider {
id?: number
name?: string
downdetector_id?: number
}
export const getProviderTool: ToolConfig<
DowndetectorGetProviderParams,
DowndetectorGetProviderResponse
> = {
id: 'downdetector_get_provider',
name: 'Downdetector Get Provider',
description:
'Get details for a Downdetector provider (ISP or network operator) by id, such as its name and Downdetector id.',
version: '1.0.0',
params: {
providerId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Downdetector provider id',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Downdetector API Bearer token',
},
},
request: {
url: (params) =>
`${DOWNDETECTOR_API_BASE}/providers/${encodePathParam(params.providerId, 'Provider ID')}`,
method: 'GET',
headers: (params) => downdetectorHeaders(params.apiKey),
},
transformResponse: async (response: Response) => {
const data: RawProvider = await response.json()
if (!response.ok) {
throw new Error(extractDowndetectorError(data, 'Failed to get provider'))
}
return {
success: true,
output: {
provider: {
id: data.id ?? null,
name: data.name ?? null,
downdetectorId: data.downdetector_id ?? null,
},
},
}
},
outputs: {
provider: {
type: 'object',
description: 'Provider details',
properties: {
id: { type: 'number', description: 'Provider id' },
name: { type: 'string', description: 'Provider name' },
downdetectorId: { type: 'number', description: 'Downdetector internal provider id' },
},
},
},
}
+113
View File
@@ -0,0 +1,113 @@
import {
DOWNDETECTOR_API_BASE,
type DowndetectorGetReportsParams,
type DowndetectorGetReportsResponse,
} from '@/tools/downdetector/types'
import { downdetectorHeaders, extractDowndetectorError } from '@/tools/downdetector/utils'
import type { ToolConfig } from '@/tools/types'
interface RawReportBucket {
point_in_time?: string
total?: number
indicators?: number
other?: number
}
export const getReportsTool: ToolConfig<
DowndetectorGetReportsParams,
DowndetectorGetReportsResponse
> = {
id: 'downdetector_get_reports',
name: 'Downdetector Get Reports',
description:
'Get the number of outage reports over time for one or more company slugs, bucketed by interval. Useful for plotting report trends or detecting spikes. Defaults to the last 24 hours.',
version: '1.0.0',
params: {
slugs: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated company slug(s) to report on. Example: "slack,zoom"',
},
startdate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 8601 start of the time range (only works together with enddate)',
},
enddate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 8601 end of the time range (only works together with startdate)',
},
interval: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Bucket interval, e.g. "15m", "1h", "1d" (default "15m")',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Downdetector API Bearer token',
},
},
request: {
url: (params) => {
// The {slugs} path segment is a comma-delimited list, so escape each slug
// individually and keep the commas literal as the array delimiter.
const slugPath = params.slugs
.split(',')
.map((slug) => encodeURIComponent(slug.trim()))
.filter((slug) => slug.length > 0)
.join(',')
if (!slugPath) {
throw new Error('At least one non-empty slug is required')
}
const url = new URL(`${DOWNDETECTOR_API_BASE}/slugs/${slugPath}/reports`)
if (params.startdate) url.searchParams.set('startdate', params.startdate)
if (params.enddate) url.searchParams.set('enddate', params.enddate)
if (params.interval) url.searchParams.set('interval', params.interval)
return url.toString()
},
method: 'GET',
headers: (params) => downdetectorHeaders(params.apiKey),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(extractDowndetectorError(data, 'Failed to get reports'))
}
const rows: RawReportBucket[] = Array.isArray(data) ? data : []
const reports = rows.map((bucket) => ({
pointInTime: bucket.point_in_time ?? null,
total: bucket.total ?? null,
indicators: bucket.indicators ?? null,
other: bucket.other ?? null,
}))
return { success: true, output: { reports } }
},
outputs: {
reports: {
type: 'array',
description: 'Report counts bucketed by interval',
items: {
type: 'object',
properties: {
pointInTime: { type: 'string', description: 'Start of the time bucket (ISO 8601)' },
total: { type: 'number', description: 'Total number of reports in the bucket' },
indicators: { type: 'number', description: 'Number of indicator reports' },
other: { type: 'number', description: 'Number of reports from other sources' },
},
},
},
},
}
@@ -0,0 +1,126 @@
import {
DOWNDETECTOR_API_BASE,
type DowndetectorGetSiteCompaniesParams,
type DowndetectorGetSiteCompaniesResponse,
} from '@/tools/downdetector/types'
import {
downdetectorHeaders,
downdetectorNextPageOutput,
encodePathParam,
extractDowndetectorError,
nextPageFromResponse,
} from '@/tools/downdetector/utils'
import type { ToolConfig } from '@/tools/types'
const DEFAULT_FIELDS = 'id,name,slug,url,status,country_iso,category_id'
interface RawSiteCompany {
id?: number
name?: string
slug?: string
url?: string
status?: string
country_iso?: string
category_id?: number
}
export const getSiteCompaniesTool: ToolConfig<
DowndetectorGetSiteCompaniesParams,
DowndetectorGetSiteCompaniesResponse
> = {
id: 'downdetector_get_site_companies',
name: 'Downdetector Get Site Companies',
description:
'List the companies monitored on a Downdetector site, including each companys current status. Useful for discovering the companies available on a regional status page.',
version: '1.0.0',
params: {
siteId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Downdetector site id',
},
fields: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Comma-separated company fields to return (defaults to id, name, slug, status)',
},
page: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Opaque page token from a previous response (X-Page-Next) for the next page',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page, between 10 and 100',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Downdetector API Bearer token',
},
},
request: {
url: (params) => {
const url = new URL(
`${DOWNDETECTOR_API_BASE}/sites/${encodePathParam(params.siteId, 'Site ID')}/companies`
)
url.searchParams.set('fields', params.fields || DEFAULT_FIELDS)
if (params.page) url.searchParams.set('page', params.page)
if (params.pageSize !== undefined) url.searchParams.set('page_size', String(params.pageSize))
return url.toString()
},
method: 'GET',
headers: (params) => downdetectorHeaders(params.apiKey),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(extractDowndetectorError(data, 'Failed to get site companies'))
}
const rows: RawSiteCompany[] = Array.isArray(data) ? data : []
const companies = rows.map((company) => ({
id: company.id ?? null,
name: company.name ?? null,
slug: company.slug ?? null,
url: company.url ?? null,
status: company.status ?? null,
countryIso: company.country_iso ?? null,
categoryId: company.category_id ?? null,
}))
return { success: true, output: { companies, nextPage: nextPageFromResponse(response) } }
},
outputs: {
companies: {
type: 'array',
description: 'List of companies on the site',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Company id' },
name: { type: 'string', description: 'Company name' },
slug: { type: 'string', description: 'Company slug' },
url: { type: 'string', description: 'Company status page URL' },
status: {
type: 'string',
description: 'Cached current status (success, warning, or danger)',
},
countryIso: { type: 'string', description: 'ISO-2 country code' },
categoryId: { type: 'number', description: 'Category id' },
},
},
},
nextPage: downdetectorNextPageOutput,
},
}
+31
View File
@@ -0,0 +1,31 @@
import { getCompanyTool } from '@/tools/downdetector/get_company'
import { getCompanyAttributionTool } from '@/tools/downdetector/get_company_attribution'
import { getCompanyBaselineTool } from '@/tools/downdetector/get_company_baseline'
import { getCompanyEventsTool } from '@/tools/downdetector/get_company_events'
import { getCompanyIncidentsTool } from '@/tools/downdetector/get_company_incidents'
import { getCompanyIndicatorsTool } from '@/tools/downdetector/get_company_indicators'
import { getCompanyLast15Tool } from '@/tools/downdetector/get_company_last_15'
import { getCompanyStatusTool } from '@/tools/downdetector/get_company_status'
import { getProviderTool } from '@/tools/downdetector/get_provider'
import { getReportsTool } from '@/tools/downdetector/get_reports'
import { getSiteCompaniesTool } from '@/tools/downdetector/get_site_companies'
import { listCategoriesTool } from '@/tools/downdetector/list_categories'
import { listIncidentsTool } from '@/tools/downdetector/list_incidents'
import { listSitesTool } from '@/tools/downdetector/list_sites'
import { searchCompaniesTool } from '@/tools/downdetector/search_companies'
export const downdetectorSearchCompaniesTool = searchCompaniesTool
export const downdetectorGetCompanyTool = getCompanyTool
export const downdetectorGetCompanyStatusTool = getCompanyStatusTool
export const downdetectorGetCompanyBaselineTool = getCompanyBaselineTool
export const downdetectorGetCompanyLast15Tool = getCompanyLast15Tool
export const downdetectorGetCompanyIndicatorsTool = getCompanyIndicatorsTool
export const downdetectorGetReportsTool = getReportsTool
export const downdetectorGetCompanyIncidentsTool = getCompanyIncidentsTool
export const downdetectorGetCompanyAttributionTool = getCompanyAttributionTool
export const downdetectorGetCompanyEventsTool = getCompanyEventsTool
export const downdetectorGetSiteCompaniesTool = getSiteCompaniesTool
export const downdetectorGetProviderTool = getProviderTool
export const downdetectorListIncidentsTool = listIncidentsTool
export const downdetectorListCategoriesTool = listCategoriesTool
export const downdetectorListSitesTool = listSitesTool
@@ -0,0 +1,70 @@
import {
DOWNDETECTOR_API_BASE,
type DowndetectorListCategoriesParams,
type DowndetectorListCategoriesResponse,
} from '@/tools/downdetector/types'
import { downdetectorHeaders, extractDowndetectorError } from '@/tools/downdetector/utils'
import type { ToolConfig } from '@/tools/types'
interface RawCategory {
id?: number
name?: string
slug?: string
}
export const listCategoriesTool: ToolConfig<
DowndetectorListCategoriesParams,
DowndetectorListCategoriesResponse
> = {
id: 'downdetector_list_categories',
name: 'Downdetector List Categories',
description:
'List all Downdetector categories (e.g. "Telecom", "Gaming", "Social Media"). Use the returned category id to filter company searches.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Downdetector API Bearer token',
},
},
request: {
url: () => `${DOWNDETECTOR_API_BASE}/categories`,
method: 'GET',
headers: (params) => downdetectorHeaders(params.apiKey),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(extractDowndetectorError(data, 'Failed to list categories'))
}
const rows: RawCategory[] = Array.isArray(data) ? data : []
const categories = rows.map((category) => ({
id: category.id ?? null,
name: category.name ?? null,
slug: category.slug ?? null,
}))
return { success: true, output: { categories } }
},
outputs: {
categories: {
type: 'array',
description: 'List of Downdetector categories',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Category id' },
name: { type: 'string', description: 'Category name' },
slug: { type: 'string', description: 'Category slug' },
},
},
},
},
}
@@ -0,0 +1,100 @@
import {
DOWNDETECTOR_API_BASE,
type DowndetectorIncidentsResponse,
type DowndetectorListIncidentsParams,
} from '@/tools/downdetector/types'
import {
downdetectorHeaders,
downdetectorIncidentItemSchema,
downdetectorNextPageOutput,
extractDowndetectorError,
mapDowndetectorIncident,
nextPageFromResponse,
} from '@/tools/downdetector/utils'
import type { ToolConfig } from '@/tools/types'
export const listIncidentsTool: ToolConfig<
DowndetectorListIncidentsParams,
DowndetectorIncidentsResponse
> = {
id: 'downdetector_list_incidents',
name: 'Downdetector List Incidents',
description:
'List all incidents (outages) across every company that were active in the chosen time period, or in the last 24 hours if no date range is provided.',
version: '1.0.0',
params: {
onlyActive: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'When true, only currently active incidents are returned',
},
startdate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 8601 start of the time range (only works together with enddate)',
},
enddate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 8601 end of the time range (only works together with startdate)',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Requested page number (1-indexed)',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page, between 10 and 100',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Downdetector API Bearer token',
},
},
request: {
url: (params) => {
const url = new URL(`${DOWNDETECTOR_API_BASE}/incidents`)
if (params.onlyActive !== undefined)
url.searchParams.set('only_active', String(params.onlyActive))
if (params.startdate) url.searchParams.set('startdate', params.startdate)
if (params.enddate) url.searchParams.set('enddate', params.enddate)
if (params.page !== undefined) url.searchParams.set('page', String(params.page))
if (params.pageSize !== undefined) url.searchParams.set('page_size', String(params.pageSize))
return url.toString()
},
method: 'GET',
headers: (params) => downdetectorHeaders(params.apiKey),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(extractDowndetectorError(data, 'Failed to list incidents'))
}
const rows = Array.isArray(data) ? data : []
const incidents = rows.map(mapDowndetectorIncident)
return { success: true, output: { incidents, nextPage: nextPageFromResponse(response) } }
},
outputs: {
incidents: {
type: 'array',
description: 'List of incidents across all companies',
items: downdetectorIncidentItemSchema,
},
nextPage: downdetectorNextPageOutput,
},
}
+71
View File
@@ -0,0 +1,71 @@
import {
DOWNDETECTOR_API_BASE,
type DowndetectorListSitesParams,
type DowndetectorListSitesResponse,
} from '@/tools/downdetector/types'
import { downdetectorHeaders, extractDowndetectorError } from '@/tools/downdetector/utils'
import type { ToolConfig } from '@/tools/types'
interface RawSite {
id?: number
name?: string
domain?: string
country_id?: number
}
export const listSitesTool: ToolConfig<DowndetectorListSitesParams, DowndetectorListSitesResponse> =
{
id: 'downdetector_list_sites',
name: 'Downdetector List Sites',
description:
'List all available Downdetector sites (regional status-page domains). Each site groups the companies monitored for a given country/region.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Downdetector API Bearer token',
},
},
request: {
url: () => `${DOWNDETECTOR_API_BASE}/sites`,
method: 'GET',
headers: (params) => downdetectorHeaders(params.apiKey),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(extractDowndetectorError(data, 'Failed to list sites'))
}
const rows: RawSite[] = Array.isArray(data) ? data : []
const sites = rows.map((site) => ({
id: site.id ?? null,
name: site.name ?? null,
domain: site.domain ?? null,
countryId: site.country_id ?? null,
}))
return { success: true, output: { sites } }
},
outputs: {
sites: {
type: 'array',
description: 'List of Downdetector sites',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Site id' },
name: { type: 'string', description: 'Site name' },
domain: { type: 'string', description: 'Site domain' },
countryId: { type: 'number', description: 'Country id for the site' },
},
},
},
},
}
@@ -0,0 +1,132 @@
import {
DOWNDETECTOR_API_BASE,
type DowndetectorSearchCompaniesParams,
type DowndetectorSearchCompaniesResponse,
} from '@/tools/downdetector/types'
import {
downdetectorHeaders,
downdetectorNextPageOutput,
extractDowndetectorError,
nextPageFromResponse,
} from '@/tools/downdetector/utils'
import type { ToolConfig } from '@/tools/types'
interface RawCompanySummary {
id?: number
name?: string
slug?: string
url?: string
country_iso?: string
category_id?: number
}
export const searchCompaniesTool: ToolConfig<
DowndetectorSearchCompaniesParams,
DowndetectorSearchCompaniesResponse
> = {
id: 'downdetector_search_companies',
name: 'Downdetector Search Companies',
description:
'Search Downdetector for monitored companies by name, slug, country, or category. Returns matching companies with their ids and slugs, which you can use with the other Downdetector operations.',
version: '1.0.0',
params: {
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Company name to filter on (partial, case-insensitive match). Example: "slack"',
},
country: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO-2 country code to filter on. Example: "US"',
},
slug: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Exact company slug to filter on. Example: "optimum-cablevision"',
},
categoryId: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Category id to filter on',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: '1-indexed page number for paginated results (default 1)',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page, between 10 and 100 (default 25)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Downdetector API Bearer token',
},
},
request: {
url: (params) => {
const url = new URL(`${DOWNDETECTOR_API_BASE}/companies/search`)
url.searchParams.set('fields', 'id,name,slug,url,country_iso,category_id')
if (params.name) url.searchParams.set('name', params.name)
if (params.country) url.searchParams.set('country', params.country)
if (params.slug) url.searchParams.set('slug', params.slug)
if (params.categoryId !== undefined)
url.searchParams.set('category_id', String(params.categoryId))
if (params.page !== undefined) url.searchParams.set('page', String(params.page))
if (params.pageSize !== undefined) url.searchParams.set('page_size', String(params.pageSize))
return url.toString()
},
method: 'GET',
headers: (params) => downdetectorHeaders(params.apiKey),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(extractDowndetectorError(data, 'Failed to search companies'))
}
const rows: RawCompanySummary[] = Array.isArray(data) ? data : []
const companies = rows.map((company) => ({
id: company.id ?? null,
name: company.name ?? null,
slug: company.slug ?? null,
url: company.url ?? null,
countryIso: company.country_iso ?? null,
categoryId: company.category_id ?? null,
}))
return { success: true, output: { companies, nextPage: nextPageFromResponse(response) } }
},
outputs: {
companies: {
type: 'array',
description: 'List of companies matching the search',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Company id' },
name: { type: 'string', description: 'Company name' },
slug: { type: 'string', description: 'Company slug' },
url: { type: 'string', description: 'Company status page URL' },
countryIso: { type: 'string', description: 'ISO-2 country code' },
categoryId: { type: 'number', description: 'Category id' },
},
},
},
nextPage: downdetectorNextPageOutput,
},
}
+318
View File
@@ -0,0 +1,318 @@
import type { ToolResponse } from '@/tools/types'
/** Base URL for the Downdetector Enterprise API (v2). */
export const DOWNDETECTOR_API_BASE = 'https://downdetectorapi.com/v2'
interface DowndetectorBaseParams {
/** Bearer access token generated from the Downdetector API dashboard. */
apiKey: string
}
/** Company summary returned by the search endpoint (restricted field set). */
export interface DowndetectorCompanySummary {
id: number | null
name: string | null
slug: string | null
url: string | null
countryIso: string | null
categoryId: number | null
}
/** Full company detail returned by the company endpoint. */
export interface DowndetectorCompany {
id: number | null
name: string | null
slug: string | null
url: string | null
status: string | null
categoryId: number | null
countryIso: string | null
siteId: number | null
baselineCurrent: number | null
stats24: number[]
baseline: number[]
indicators: string[]
description: string | null
}
/** A single reported indicator (e.g. "App crashing", "Login") for a company. */
export interface DowndetectorIndicator {
slug: string | null
indicator: string | null
key: string | null
amount: number | null
percentage: number | null
}
/** A time bucket of report counts. */
export interface DowndetectorReportBucket {
pointInTime: string | null
total: number | null
indicators: number | null
other: number | null
}
/** An incident (outage) detected for a company. */
export interface DowndetectorIncident {
id: number | null
createdAt: string | null
resolvedAt: string | null
isActive: boolean | null
peakAttribution: number | null
peakUserImpact: number | null
total: number | null
indicators: number | null
other: number | null
updatedAt: string | null
}
/** A Downdetector category (e.g. "Telecom", "Gaming"). */
export interface DowndetectorCategory {
id: number | null
name: string | null
slug: string | null
}
/** A Downdetector site (regional status page domain). */
export interface DowndetectorSite {
id: number | null
name: string | null
domain: string | null
countryId: number | null
}
/** Company summary including current status (returned by the site companies endpoint). */
export interface DowndetectorSiteCompany {
id: number | null
name: string | null
slug: string | null
url: string | null
status: string | null
countryIso: string | null
categoryId: number | null
}
/** An event (e.g. a detected outage) published for a company. */
export interface DowndetectorEvent {
id: number | null
title: string | null
body: string | null
companyId: number | null
createdAt: string | null
publishAt: string | null
isActive: boolean | null
measurement: {
startedOn: string | null
endedOn: string | null
expected: number | null
actual: number | null
} | null
}
/** Incident attribution detail for a company. */
export interface DowndetectorAttribution {
attribution: number | null
attributionCalculatedAt: string | null
userImpact: number | null
userImpactCalculatedAt: string | null
reason: number | null
dangerDurationS: number | null
incidentId: number | null
incidentCreatedAt: string | null
}
/** A Downdetector provider (ISP / network operator). */
export interface DowndetectorProvider {
id: number | null
name: string | null
downdetectorId: number | null
}
export interface DowndetectorSearchCompaniesParams extends DowndetectorBaseParams {
name?: string
country?: string
slug?: string
categoryId?: number
page?: number
pageSize?: number
}
export interface DowndetectorSearchCompaniesResponse extends ToolResponse {
output: {
companies: DowndetectorCompanySummary[]
nextPage: string | null
}
}
export interface DowndetectorGetCompanyParams extends DowndetectorBaseParams {
companyId: string
fields?: string
}
export interface DowndetectorGetCompanyResponse extends ToolResponse {
output: {
company: DowndetectorCompany
}
}
export interface DowndetectorGetCompanyStatusParams extends DowndetectorBaseParams {
companyId: string
threshold?: number
}
export interface DowndetectorGetCompanyStatusResponse extends ToolResponse {
output: {
status: string
}
}
export interface DowndetectorGetCompanyBaselineParams extends DowndetectorBaseParams {
companyId: string
}
export interface DowndetectorGetCompanyBaselineResponse extends ToolResponse {
output: {
baseline: number
}
}
export interface DowndetectorGetCompanyIndicatorsParams extends DowndetectorBaseParams {
companyId: string
startdate?: string
enddate?: string
}
export interface DowndetectorGetCompanyIndicatorsResponse extends ToolResponse {
output: {
indicators: DowndetectorIndicator[]
}
}
export interface DowndetectorGetReportsParams extends DowndetectorBaseParams {
slugs: string
startdate?: string
enddate?: string
interval?: string
}
export interface DowndetectorGetReportsResponse extends ToolResponse {
output: {
reports: DowndetectorReportBucket[]
}
}
export interface DowndetectorGetCompanyIncidentsParams extends DowndetectorBaseParams {
companyId: string
onlyActive?: boolean
startdate?: string
enddate?: string
page?: number
pageSize?: number
}
export interface DowndetectorListIncidentsParams extends DowndetectorBaseParams {
onlyActive?: boolean
startdate?: string
enddate?: string
page?: number
pageSize?: number
}
export interface DowndetectorIncidentsResponse extends ToolResponse {
output: {
incidents: DowndetectorIncident[]
nextPage: string | null
}
}
export interface DowndetectorListCategoriesParams extends DowndetectorBaseParams {}
export interface DowndetectorListCategoriesResponse extends ToolResponse {
output: {
categories: DowndetectorCategory[]
}
}
export interface DowndetectorListSitesParams extends DowndetectorBaseParams {}
export interface DowndetectorListSitesResponse extends ToolResponse {
output: {
sites: DowndetectorSite[]
}
}
export interface DowndetectorGetCompanyLast15Params extends DowndetectorBaseParams {
companyId: string
}
export interface DowndetectorGetCompanyLast15Response extends ToolResponse {
output: {
count: number
}
}
export interface DowndetectorGetCompanyEventsParams extends DowndetectorBaseParams {
companyId: string
startdate?: string
enddate?: string
page?: number
pageSize?: number
}
export interface DowndetectorGetCompanyEventsResponse extends ToolResponse {
output: {
events: DowndetectorEvent[]
nextPage: string | null
}
}
export interface DowndetectorGetCompanyAttributionParams extends DowndetectorBaseParams {
companyId: string
}
export interface DowndetectorGetCompanyAttributionResponse extends ToolResponse {
output: {
attribution: DowndetectorAttribution
}
}
export interface DowndetectorGetSiteCompaniesParams extends DowndetectorBaseParams {
siteId: string
fields?: string
/** Opaque page token from a previous response's `X-Page-Next` header. */
page?: string
pageSize?: number
}
export interface DowndetectorGetSiteCompaniesResponse extends ToolResponse {
output: {
companies: DowndetectorSiteCompany[]
nextPage: string | null
}
}
export interface DowndetectorGetProviderParams extends DowndetectorBaseParams {
providerId: string
}
export interface DowndetectorGetProviderResponse extends ToolResponse {
output: {
provider: DowndetectorProvider
}
}
export type DowndetectorResponse =
| DowndetectorSearchCompaniesResponse
| DowndetectorGetCompanyResponse
| DowndetectorGetCompanyStatusResponse
| DowndetectorGetCompanyBaselineResponse
| DowndetectorGetCompanyIndicatorsResponse
| DowndetectorGetReportsResponse
| DowndetectorIncidentsResponse
| DowndetectorListCategoriesResponse
| DowndetectorListSitesResponse
| DowndetectorGetCompanyLast15Response
| DowndetectorGetCompanyEventsResponse
| DowndetectorGetCompanyAttributionResponse
| DowndetectorGetSiteCompaniesResponse
| DowndetectorGetProviderResponse
+105
View File
@@ -0,0 +1,105 @@
import type { DowndetectorIncident } from '@/tools/downdetector/types'
interface RawIncident {
id?: number
created_at?: string
resolved_at?: string
is_active?: boolean
peak_attribution?: number
peak_user_impact?: number
total?: number
indicators?: number
other?: number
updated_at?: string
}
/** Map a raw Downdetector incident object to the camelCased output shape. */
export function mapDowndetectorIncident(incident: RawIncident): DowndetectorIncident {
return {
id: incident.id ?? null,
createdAt: incident.created_at ?? null,
resolvedAt: incident.resolved_at ?? null,
isActive: incident.is_active ?? null,
peakAttribution: incident.peak_attribution ?? null,
peakUserImpact: incident.peak_user_impact ?? null,
total: incident.total ?? null,
indicators: incident.indicators ?? null,
other: incident.other ?? null,
updatedAt: incident.updated_at ?? null,
}
}
/** Output schema for an incident object, shared by the incident tools. */
export const downdetectorIncidentItemSchema = {
type: 'object',
properties: {
id: { type: 'number', description: 'Incident id' },
createdAt: { type: 'string', description: 'ISO 8601 timestamp when the incident was created' },
resolvedAt: {
type: 'string',
description: 'ISO 8601 timestamp when the incident was resolved (null if active)',
},
isActive: { type: 'boolean', description: 'Whether the incident is currently active' },
peakAttribution: {
type: 'number',
description: 'Peak attribution enum (0 N/A, 1 undetermined, 2 external, 3 internal)',
},
peakUserImpact: {
type: 'number',
description: 'Peak user impact enum (0 low, 1 medium, 2 high, 3 very high)',
},
total: { type: 'number', description: 'Total reports during the incident' },
indicators: { type: 'number', description: 'Number of indicator reports during the incident' },
other: { type: 'number', description: 'Number of other reports during the incident' },
updatedAt: { type: 'string', description: 'ISO 8601 timestamp when the incident was updated' },
},
} as const
/**
* Read the next-page cursor from a Downdetector response. Paged endpoints return
* the value to send back as `?page=...` in the `X-Page-Next` header; an absent
* header means the current page is the last one.
*/
export function nextPageFromResponse(response: Response): string | null {
return response.headers.get('X-Page-Next') || null
}
/** Output schema for the `nextPage` cursor, shared by the paged tools. */
export const downdetectorNextPageOutput = {
type: 'string',
description: 'Cursor to pass back as the next page (X-Page-Next); null when on the last page',
optional: true,
} as const
/**
* Trim, validate, and URL-encode a required path parameter. Throws a clear error
* when the value is empty or whitespace-only so the tool never issues a malformed
* request like `/companies//status`.
*/
export function encodePathParam(value: string, name: string): string {
const trimmed = value.trim()
if (!trimmed) {
throw new Error(`${name} is required`)
}
return encodeURIComponent(trimmed)
}
/** Standard request headers for the Downdetector API (Bearer auth). */
export function downdetectorHeaders(apiKey: string): Record<string, string> {
return {
Accept: 'application/json',
Authorization: `Bearer ${apiKey}`,
}
}
/**
* Extract a human-readable error message from a Downdetector error response.
* The API returns `{ error: true, message: string }` on failure.
*/
export function extractDowndetectorError(data: unknown, fallback: string): string {
if (data && typeof data === 'object' && 'message' in data) {
const message = (data as { message?: unknown }).message
if (typeof message === 'string' && message.length > 0) return message
}
return fallback
}