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
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:
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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 company’s 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,
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user