ClinicalTrials.gov: Searches and retrieves clinical trial records from the public ClinicalTrials.gov API v2, no key required.
| 1 | import { definePlugin, defineSource } from '@jaspers-ai/sdk' |
| 2 | import { z } from 'zod' |
| 3 | import { getJson, HOST, searchQuery } from './client' |
| 4 | import { detailRow, summaryRow, type Study } from './rows' |
| 5 | |
| 6 | // ClinicalTrials.gov API v2: search studies and read one study, no key. |
| 7 | |
| 8 | const PHASES = ['EARLY_PHASE1', 'PHASE1', 'PHASE2', 'PHASE3', 'PHASE4', 'NA'] as const |
| 9 | const STATUSES = [ |
| 10 | 'RECRUITING', |
| 11 | 'NOT_YET_RECRUITING', |
| 12 | 'ACTIVE_NOT_RECRUITING', |
| 13 | 'ENROLLING_BY_INVITATION', |
| 14 | 'COMPLETED', |
| 15 | 'SUSPENDED', |
| 16 | 'TERMINATED', |
| 17 | 'WITHDRAWN', |
| 18 | 'UNKNOWN', |
| 19 | ] as const |
| 20 | const date = z |
| 21 | .string() |
| 22 | .regex(/^\d{4}(-\d{2}(-\d{2})?)?$/) |
| 23 | .optional() |
| 24 | |
| 25 | const searchStudies = defineSource({ |
| 26 | description: |
| 27 | 'Search ClinicalTrials.gov studies by condition, intervention/drug, sponsor, free text, phase, overall status, and start or primary completion date range. One row per study: nctId, briefTitle, leadSponsor, collaborators, phases, overallStatus, startDate, primaryCompletionDate (with its ACTUAL/ESTIMATED type), completionDate, conditions, interventions, enrollment, studyType, lastUpdatePostDate, url. meta has totalCount and nextPageToken (pass it back as pageToken for the next page). For catalysts: sponsor, phases [PHASE3], statuses [RECRUITING, ACTIVE_NOT_RECRUITING], primaryCompletionFrom today, sort PrimaryCompletionDate:asc.', |
| 28 | hosts: [HOST], |
| 29 | input: z.object({ |
| 30 | condition: z.string().optional().describe('Condition or disease (query.cond), like "non-small cell lung cancer".'), |
| 31 | intervention: z.string().optional().describe('Intervention or drug name (query.intr), like "pembrolizumab".'), |
| 32 | sponsor: z.string().optional().describe('Sponsor or collaborator (query.spons), like "Pfizer".'), |
| 33 | term: z.string().optional().describe('Free-text search over the whole record (query.term).'), |
| 34 | phases: z.array(z.enum(PHASES)).optional().describe('Phases to match, any of them: PHASE1, PHASE2, PHASE3, PHASE4, EARLY_PHASE1, NA.'), |
| 35 | statuses: z.array(z.enum(STATUSES)).optional().describe('Overall statuses to match (filter.overallStatus).'), |
| 36 | startFrom: date.describe('Earliest start date, YYYY-MM-DD (or YYYY-MM).'), |
| 37 | startTo: date.describe('Latest start date, YYYY-MM-DD.'), |
| 38 | primaryCompletionFrom: date.describe('Earliest primary completion date, YYYY-MM-DD.'), |
| 39 | primaryCompletionTo: date.describe('Latest primary completion date, YYYY-MM-DD.'), |
| 40 | sort: z |
| 41 | .string() |
| 42 | .optional() |
| 43 | .describe('Sort as Field:asc or Field:desc, like LastUpdatePostDate:desc, StartDate:desc, PrimaryCompletionDate:asc; a bare field sorts ascending. Default is relevance.'), |
| 44 | pageSize: z.number().int().min(1).max(1000).default(50).describe('Studies per page, up to 1000.'), |
| 45 | pageToken: z.string().optional().describe('meta.nextPageToken from the previous page.'), |
| 46 | }), |
| 47 | async run(args, ctx) { |
| 48 | const page = (await getJson(ctx, '/studies', searchQuery(args))) as { |
| 49 | studies?: Study[] |
| 50 | totalCount?: number |
| 51 | nextPageToken?: string |
| 52 | } |
| 53 | return { |
| 54 | rows: (page.studies ?? []).map(summaryRow), |
| 55 | totalCount: page.totalCount ?? null, |
| 56 | nextPageToken: page.nextPageToken ?? null, |
| 57 | } |
| 58 | }, |
| 59 | }) |
| 60 | |
| 61 | const getStudy = defineSource({ |
| 62 | description: |
| 63 | "One ClinicalTrials.gov study by NCT ID: the search row's fields plus officialTitle, briefSummary, primaryOutcomes, arms, interventions, eligibility (sex, ages, healthy volunteers, criteria), locationsCount, hasResults, studyFirstPostDate, resultsFirstPostDate.", |
| 64 | hosts: [HOST], |
| 65 | input: z.object({ |
| 66 | nctId: z.string().regex(/^NCT\d{8}$/i).describe('The NCT ID, like NCT04368728.'), |
| 67 | }), |
| 68 | async run(args, ctx) { |
| 69 | const study = (await getJson(ctx, `/studies/${args.nctId.toUpperCase()}`, new URLSearchParams({ format: 'json' }))) as Study |
| 70 | return [detailRow(study)] |
| 71 | }, |
| 72 | }) |
| 73 | |
| 74 | export default definePlugin({ |
| 75 | id: 'clinicaltrials', |
| 76 | sources: { 'search-studies': searchStudies, 'get-study': getStudy }, |
| 77 | }) |