feat(ashby): add 15 new tools and fix existing tool accuracy (#3662)

* feat(ashby): add 15 new tools and fix existing tool accuracy

* fix(ashby): fix response field mappings for changeStage and createNote

* fix(ashby): fix websiteUrl field name in candidate.update request

* fix(ashby): revert body field names to candidateId and jobId for info endpoints

* fix(ashby): add subblock ID migrations for removed emailType and phoneType

* fix(ashby): map removed emailType/phoneType to dummy keys to avoid data corruption
This commit is contained in:
Waleed
2026-03-18 22:12:16 -07:00
committed by GitHub
parent 638063cac1
commit 12908c14be
29 changed files with 1782 additions and 305 deletions
+186 -65
View File
@@ -7,7 +7,7 @@ export const AshbyBlock: BlockConfig = {
name: 'Ashby',
description: 'Manage candidates, jobs, and applications in Ashby',
longDescription:
'Integrate Ashby into the workflow. Can list, search, create, and update candidates, list and get job details, create notes, list notes, list and get applications, create applications, and list offers.',
'Integrate Ashby into the workflow. Manage candidates (list, get, create, update, search, tag), applications (list, get, create, change stage), jobs (list, get), job postings (list, get), offers (list, get), notes (list, create), interviews (list), and reference data (sources, tags, archive reasons, custom fields, departments, locations, openings, users).',
docsLink: 'https://docs.sim.ai/tools/ashby',
category: 'tools',
bgColor: '#5D4ED6',
@@ -45,6 +45,21 @@ export const AshbyBlock: BlockConfig = {
{ label: 'Get Application', id: 'get_application' },
{ label: 'Create Application', id: 'create_application' },
{ label: 'List Offers', id: 'list_offers' },
{ label: 'Change Application Stage', id: 'change_application_stage' },
{ label: 'Add Candidate Tag', id: 'add_candidate_tag' },
{ label: 'Remove Candidate Tag', id: 'remove_candidate_tag' },
{ label: 'Get Offer', id: 'get_offer' },
{ label: 'List Sources', id: 'list_sources' },
{ label: 'List Candidate Tags', id: 'list_candidate_tags' },
{ label: 'List Archive Reasons', id: 'list_archive_reasons' },
{ label: 'List Custom Fields', id: 'list_custom_fields' },
{ label: 'List Departments', id: 'list_departments' },
{ label: 'List Locations', id: 'list_locations' },
{ label: 'List Job Postings', id: 'list_job_postings' },
{ label: 'Get Job Posting', id: 'get_job_posting' },
{ label: 'List Openings', id: 'list_openings' },
{ label: 'List Users', id: 'list_users' },
{ label: 'List Interviews', id: 'list_interviews' },
],
value: () => 'list_candidates',
},
@@ -56,24 +71,34 @@ export const AshbyBlock: BlockConfig = {
placeholder: 'Enter your Ashby API key',
password: true,
},
// Get Candidate / Create Note / List Notes / Update Candidate - candidateId
{
id: 'candidateId',
title: 'Candidate ID',
type: 'short-input',
required: {
field: 'operation',
value: ['get_candidate', 'create_note', 'list_notes', 'update_candidate'],
value: [
'get_candidate',
'create_note',
'list_notes',
'update_candidate',
'add_candidate_tag',
'remove_candidate_tag',
],
},
placeholder: 'Enter candidate UUID',
condition: {
field: 'operation',
value: ['get_candidate', 'create_note', 'list_notes', 'update_candidate'],
value: [
'get_candidate',
'create_note',
'list_notes',
'update_candidate',
'add_candidate_tag',
'remove_candidate_tag',
],
},
},
// Create Candidate fields
{
id: 'name',
title: 'Name',
@@ -86,22 +111,10 @@ export const AshbyBlock: BlockConfig = {
id: 'email',
title: 'Email',
type: 'short-input',
required: { field: 'operation', value: 'create_candidate' },
placeholder: 'Email address',
condition: { field: 'operation', value: ['create_candidate', 'update_candidate'] },
},
{
id: 'emailType',
title: 'Email Type',
type: 'dropdown',
options: [
{ label: 'Work', id: 'Work' },
{ label: 'Personal', id: 'Personal' },
{ label: 'Other', id: 'Other' },
],
value: () => 'Work',
condition: { field: 'operation', value: ['create_candidate', 'update_candidate'] },
mode: 'advanced',
},
{
id: 'phoneNumber',
title: 'Phone Number',
@@ -110,19 +123,6 @@ export const AshbyBlock: BlockConfig = {
condition: { field: 'operation', value: ['create_candidate', 'update_candidate'] },
mode: 'advanced',
},
{
id: 'phoneType',
title: 'Phone Type',
type: 'dropdown',
options: [
{ label: 'Work', id: 'Work' },
{ label: 'Personal', id: 'Personal' },
{ label: 'Other', id: 'Other' },
],
value: () => 'Work',
condition: { field: 'operation', value: ['create_candidate', 'update_candidate'] },
mode: 'advanced',
},
{
id: 'linkedInUrl',
title: 'LinkedIn URL',
@@ -150,8 +150,6 @@ export const AshbyBlock: BlockConfig = {
},
mode: 'advanced',
},
// Update Candidate fields
{
id: 'updateName',
title: 'Name',
@@ -168,8 +166,6 @@ export const AshbyBlock: BlockConfig = {
condition: { field: 'operation', value: 'update_candidate' },
mode: 'advanced',
},
// Search Candidates fields
{
id: 'searchName',
title: 'Name',
@@ -184,8 +180,6 @@ export const AshbyBlock: BlockConfig = {
placeholder: 'Search by candidate email',
condition: { field: 'operation', value: 'search_candidates' },
},
// Get Job fields
{
id: 'jobId',
title: 'Job ID',
@@ -194,18 +188,20 @@ export const AshbyBlock: BlockConfig = {
placeholder: 'Enter job UUID',
condition: { field: 'operation', value: ['get_job', 'create_application'] },
},
// Get Application fields
{
id: 'applicationId',
title: 'Application ID',
type: 'short-input',
required: { field: 'operation', value: 'get_application' },
required: {
field: 'operation',
value: ['get_application', 'change_application_stage'],
},
placeholder: 'Enter application UUID',
condition: { field: 'operation', value: 'get_application' },
condition: {
field: 'operation',
value: ['get_application', 'change_application_stage', 'list_interviews'],
},
},
// Create Application fields
{
id: 'appCandidateId',
title: 'Candidate ID',
@@ -226,9 +222,12 @@ export const AshbyBlock: BlockConfig = {
id: 'interviewStageId',
title: 'Interview Stage ID',
type: 'short-input',
placeholder: 'Interview stage UUID (defaults to first Lead stage)',
condition: { field: 'operation', value: 'create_application' },
mode: 'advanced',
required: { field: 'operation', value: 'change_application_stage' },
placeholder: 'Interview stage UUID',
condition: {
field: 'operation',
value: ['create_application', 'change_application_stage', 'list_interviews'],
},
},
{
id: 'creditedToUserId',
@@ -257,8 +256,6 @@ Output only the ISO 8601 timestamp string, nothing else.`,
generationType: 'timestamp',
},
},
// Create Note fields
{
id: 'note',
title: 'Note',
@@ -286,8 +283,6 @@ Output only the ISO 8601 timestamp string, nothing else.`,
condition: { field: 'operation', value: 'create_note' },
mode: 'advanced',
},
// List Applications filter fields
{
id: 'filterStatus',
title: 'Status Filter',
@@ -338,8 +333,6 @@ Output only the ISO 8601 timestamp string, nothing else.`,
generationType: 'timestamp',
},
},
// List Jobs status filter
{
id: 'jobStatus',
title: 'Status Filter',
@@ -355,8 +348,6 @@ Output only the ISO 8601 timestamp string, nothing else.`,
condition: { field: 'operation', value: 'list_jobs' },
mode: 'advanced',
},
// Pagination fields for list operations
{
id: 'cursor',
title: 'Cursor',
@@ -364,7 +355,16 @@ Output only the ISO 8601 timestamp string, nothing else.`,
placeholder: 'Pagination cursor from previous response',
condition: {
field: 'operation',
value: ['list_candidates', 'list_jobs', 'list_applications', 'list_notes', 'list_offers'],
value: [
'list_candidates',
'list_jobs',
'list_applications',
'list_notes',
'list_offers',
'list_openings',
'list_users',
'list_interviews',
],
},
mode: 'advanced',
},
@@ -375,12 +375,57 @@ Output only the ISO 8601 timestamp string, nothing else.`,
placeholder: 'Results per page (default 100)',
condition: {
field: 'operation',
value: ['list_candidates', 'list_jobs', 'list_applications', 'list_notes', 'list_offers'],
value: [
'list_candidates',
'list_jobs',
'list_applications',
'list_notes',
'list_offers',
'list_openings',
'list_users',
'list_interviews',
],
},
mode: 'advanced',
},
// Trigger subBlocks
{
id: 'tagId',
title: 'Tag ID',
type: 'short-input',
required: {
field: 'operation',
value: ['add_candidate_tag', 'remove_candidate_tag'],
},
placeholder: 'Enter tag UUID',
condition: {
field: 'operation',
value: ['add_candidate_tag', 'remove_candidate_tag'],
},
},
{
id: 'archiveReasonId',
title: 'Archive Reason ID',
type: 'short-input',
placeholder: 'Archive reason UUID (required for Archived stages)',
condition: { field: 'operation', value: 'change_application_stage' },
mode: 'advanced',
},
{
id: 'offerId',
title: 'Offer ID',
type: 'short-input',
required: { field: 'operation', value: 'get_offer' },
placeholder: 'Enter offer UUID',
condition: { field: 'operation', value: 'get_offer' },
},
{
id: 'jobPostingId',
title: 'Job Posting ID',
type: 'short-input',
required: { field: 'operation', value: 'get_job_posting' },
placeholder: 'Enter job posting UUID',
condition: { field: 'operation', value: 'get_job_posting' },
},
...getTrigger('ashby_application_submit').subBlocks,
...getTrigger('ashby_candidate_stage_change').subBlocks,
...getTrigger('ashby_candidate_hire').subBlocks,
@@ -391,17 +436,32 @@ Output only the ISO 8601 timestamp string, nothing else.`,
tools: {
access: [
'ashby_add_candidate_tag',
'ashby_change_application_stage',
'ashby_create_application',
'ashby_create_candidate',
'ashby_create_note',
'ashby_get_application',
'ashby_get_candidate',
'ashby_get_job',
'ashby_get_job_posting',
'ashby_get_offer',
'ashby_list_applications',
'ashby_list_archive_reasons',
'ashby_list_candidate_tags',
'ashby_list_candidates',
'ashby_list_custom_fields',
'ashby_list_departments',
'ashby_list_interviews',
'ashby_list_job_postings',
'ashby_list_jobs',
'ashby_list_locations',
'ashby_list_notes',
'ashby_list_offers',
'ashby_list_openings',
'ashby_list_sources',
'ashby_list_users',
'ashby_remove_candidate_tag',
'ashby_search_candidates',
'ashby_update_candidate',
],
@@ -419,10 +479,8 @@ Output only the ISO 8601 timestamp string, nothing else.`,
if (params.sendNotifications === 'true' || params.sendNotifications === true) {
result.sendNotifications = true
}
// Create Application params
if (params.appCandidateId) result.candidateId = params.appCandidateId
if (params.appCreatedAt) result.createdAt = params.appCreatedAt
// Update Candidate params
if (params.updateName) result.name = params.updateName
return result
},
@@ -435,9 +493,7 @@ Output only the ISO 8601 timestamp string, nothing else.`,
candidateId: { type: 'string', description: 'Candidate UUID' },
name: { type: 'string', description: 'Candidate full name' },
email: { type: 'string', description: 'Email address' },
emailType: { type: 'string', description: 'Email type (Personal, Work, Other)' },
phoneNumber: { type: 'string', description: 'Phone number' },
phoneType: { type: 'string', description: 'Phone type (Personal, Work, Other)' },
linkedInUrl: { type: 'string', description: 'LinkedIn profile URL' },
githubUrl: { type: 'string', description: 'GitHub profile URL' },
websiteUrl: { type: 'string', description: 'Personal website URL' },
@@ -462,6 +518,10 @@ Output only the ISO 8601 timestamp string, nothing else.`,
jobStatus: { type: 'string', description: 'Job status filter' },
cursor: { type: 'string', description: 'Pagination cursor' },
perPage: { type: 'number', description: 'Results per page' },
tagId: { type: 'string', description: 'Tag UUID' },
offerId: { type: 'string', description: 'Offer UUID' },
jobPostingId: { type: 'string', description: 'Job posting UUID' },
archiveReasonId: { type: 'string', description: 'Archive reason UUID' },
},
outputs: {
@@ -486,12 +546,73 @@ Output only the ISO 8601 timestamp string, nothing else.`,
},
offers: {
type: 'json',
description: 'List of offers (id, status, candidate, job, createdAt, updatedAt)',
description:
'List of offers (id, offerStatus, acceptanceStatus, applicationId, startDate, salary, openingId)',
},
archiveReasons: {
type: 'json',
description: 'List of archive reasons (id, text, reasonType, isArchived)',
},
sources: {
type: 'json',
description: 'List of sources (id, title, isArchived)',
},
customFields: {
type: 'json',
description: 'List of custom fields (id, title, fieldType, objectType, isArchived)',
},
departments: {
type: 'json',
description: 'List of departments (id, name, isArchived, parentId)',
},
locations: {
type: 'json',
description: 'List of locations (id, name, isArchived, isRemote, address)',
},
jobPostings: {
type: 'json',
description:
'List of job postings (id, title, jobId, locationName, departmentName, employmentType, isListed, publishedDate)',
},
openings: {
type: 'json',
description: 'List of openings (id, openingState, isArchived, openedAt, closedAt)',
},
users: {
type: 'json',
description: 'List of users (id, firstName, lastName, email, isEnabled, globalRole)',
},
interviewSchedules: {
type: 'json',
description:
'List of interview schedules (id, applicationId, interviewStageId, status, createdAt)',
},
tags: {
type: 'json',
description: 'List of candidate tags (id, title, isArchived)',
},
stageId: { type: 'string', description: 'Interview stage UUID after stage change' },
success: { type: 'boolean', description: 'Whether the operation succeeded' },
offerStatus: {
type: 'string',
description: 'Offer status (e.g. WaitingOnCandidateResponse, CandidateAccepted)',
},
acceptanceStatus: {
type: 'string',
description: 'Acceptance status (e.g. Accepted, Declined, Pending)',
},
applicationId: { type: 'string', description: 'Associated application UUID' },
openingId: { type: 'string', description: 'Opening UUID associated with the offer' },
salary: {
type: 'json',
description: 'Salary details from latest version (currencyCode, value)',
},
startDate: { type: 'string', description: 'Offer start date from latest version' },
id: { type: 'string', description: 'Resource UUID' },
name: { type: 'string', description: 'Resource name' },
title: { type: 'string', description: 'Job title' },
status: { type: 'string', description: 'Status' },
noteId: { type: 'string', description: 'Created note UUID' },
content: { type: 'string', description: 'Note content' },
moreDataAvailable: { type: 'boolean', description: 'Whether more pages exist' },
nextCursor: { type: 'string', description: 'Pagination cursor for next page' },
@@ -23,6 +23,10 @@ export const SUBBLOCK_ID_MIGRATIONS: Record<string, Record<string, string>> = {
knowledge: {
knowledgeBaseId: 'knowledgeBaseSelector',
},
ashby: {
emailType: '_removed_emailType',
phoneType: '_removed_phoneType',
},
}
/**
+76
View File
@@ -0,0 +1,76 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyAddCandidateTagParams {
apiKey: string
candidateId: string
tagId: string
}
interface AshbyAddCandidateTagResponse extends ToolResponse {
output: {
success: boolean
}
}
export const addCandidateTagTool: ToolConfig<
AshbyAddCandidateTagParams,
AshbyAddCandidateTagResponse
> = {
id: 'ashby_add_candidate_tag',
name: 'Ashby Add Candidate Tag',
description: 'Adds a tag to a candidate in Ashby.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ashby API Key',
},
candidateId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The UUID of the candidate to add the tag to',
},
tagId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The UUID of the tag to add',
},
},
request: {
url: 'https://api.ashbyhq.com/candidate.addTag',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: (params) => ({
candidateId: params.candidateId,
tagId: params.tagId,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.errorInfo?.message || 'Failed to add tag to candidate')
}
return {
success: true,
output: {
success: true,
},
}
},
outputs: {
success: { type: 'boolean', description: 'Whether the tag was successfully added' },
},
}
@@ -0,0 +1,94 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyChangeApplicationStageParams {
apiKey: string
applicationId: string
interviewStageId: string
archiveReasonId?: string
}
interface AshbyChangeApplicationStageResponse extends ToolResponse {
output: {
applicationId: string
stageId: string | null
}
}
export const changeApplicationStageTool: ToolConfig<
AshbyChangeApplicationStageParams,
AshbyChangeApplicationStageResponse
> = {
id: 'ashby_change_application_stage',
name: 'Ashby Change Application Stage',
description:
'Moves an application to a different interview stage. Requires an archive reason when moving to an Archived stage.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ashby API Key',
},
applicationId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The UUID of the application to update the stage of',
},
interviewStageId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The UUID of the interview stage to move the application to',
},
archiveReasonId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Archive reason UUID. Required when moving to an Archived stage, ignored otherwise',
},
},
request: {
url: 'https://api.ashbyhq.com/application.changeStage',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: (params) => {
const body: Record<string, unknown> = {
applicationId: params.applicationId,
interviewStageId: params.interviewStageId,
}
if (params.archiveReasonId) body.archiveReasonId = params.archiveReasonId
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.errorInfo?.message || 'Failed to change application stage')
}
const r = data.results
return {
success: true,
output: {
applicationId: r.id ?? null,
stageId: r.currentInterviewStage?.id ?? null,
},
}
},
outputs: {
applicationId: { type: 'string', description: 'Application UUID' },
stageId: { type: 'string', description: 'New interview stage UUID' },
},
}
+3 -85
View File
@@ -13,27 +13,7 @@ interface AshbyCreateApplicationParams {
interface AshbyCreateApplicationResponse extends ToolResponse {
output: {
id: string
status: string
candidate: {
id: string
name: string
}
job: {
id: string
title: string
}
currentInterviewStage: {
id: string
title: string
type: string
} | null
source: {
id: string
title: string
} | null
createdAt: string
updatedAt: string
applicationId: string
}
}
@@ -132,74 +112,12 @@ export const createApplicationTool: ToolConfig<
return {
success: true,
output: {
id: r.id ?? null,
status: r.status ?? null,
candidate: {
id: r.candidate?.id ?? null,
name: r.candidate?.name ?? null,
},
job: {
id: r.job?.id ?? null,
title: r.job?.title ?? null,
},
currentInterviewStage: r.currentInterviewStage
? {
id: r.currentInterviewStage.id ?? null,
title: r.currentInterviewStage.title ?? null,
type: r.currentInterviewStage.type ?? null,
}
: null,
source: r.source
? {
id: r.source.id ?? null,
title: r.source.title ?? null,
}
: null,
createdAt: r.createdAt ?? null,
updatedAt: r.updatedAt ?? null,
applicationId: r.applicationId ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Created application UUID' },
status: { type: 'string', description: 'Application status (Active, Hired, Archived, Lead)' },
candidate: {
type: 'object',
description: 'Associated candidate',
properties: {
id: { type: 'string', description: 'Candidate UUID' },
name: { type: 'string', description: 'Candidate name' },
},
},
job: {
type: 'object',
description: 'Associated job',
properties: {
id: { type: 'string', description: 'Job UUID' },
title: { type: 'string', description: 'Job title' },
},
},
currentInterviewStage: {
type: 'object',
description: 'Current interview stage',
optional: true,
properties: {
id: { type: 'string', description: 'Stage UUID' },
title: { type: 'string', description: 'Stage title' },
type: { type: 'string', description: 'Stage type' },
},
},
source: {
type: 'object',
description: 'Application source',
optional: true,
properties: {
id: { type: 'string', description: 'Source UUID' },
title: { type: 'string', description: 'Source title' },
},
},
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
updatedAt: { type: 'string', description: 'ISO 8601 last update timestamp' },
applicationId: { type: 'string', description: 'Created application UUID' },
},
}
+5 -33
View File
@@ -25,28 +25,16 @@ export const createCandidateTool: ToolConfig<
},
email: {
type: 'string',
required: false,
required: true,
visibility: 'user-or-llm',
description: 'Primary email address for the candidate',
},
emailType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Email address type: Personal, Work, or Other (default Work)',
},
phoneNumber: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Primary phone number for the candidate',
},
phoneType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Phone number type: Personal, Work, or Other (default Work)',
},
linkedInUrl: {
type: 'string',
required: false,
@@ -77,27 +65,11 @@ export const createCandidateTool: ToolConfig<
body: (params) => {
const body: Record<string, unknown> = {
name: params.name,
email: params.email,
}
if (params.email) {
body.primaryEmailAddress = {
value: params.email,
type: params.emailType || 'Work',
isPrimary: true,
}
}
if (params.phoneNumber) {
body.primaryPhoneNumber = {
value: params.phoneNumber,
type: params.phoneType || 'Work',
isPrimary: true,
}
}
if (params.linkedInUrl || params.githubUrl) {
const socialLinks: Array<{ url: string; type: string }> = []
if (params.linkedInUrl) socialLinks.push({ url: params.linkedInUrl, type: 'LinkedIn' })
if (params.githubUrl) socialLinks.push({ url: params.githubUrl, type: 'GitHub' })
body.socialLinks = socialLinks
}
if (params.phoneNumber) body.phoneNumber = params.phoneNumber
if (params.linkedInUrl) body.linkedInUrl = params.linkedInUrl
if (params.githubUrl) body.githubUrl = params.githubUrl
if (params.sourceId) body.sourceId = params.sourceId
return body
},
+2 -25
View File
@@ -78,35 +78,12 @@ export const createNoteTool: ToolConfig<AshbyCreateNoteParams, AshbyCreateNoteRe
return {
success: true,
output: {
id: r.id ?? null,
content: r.content ?? null,
author: r.author
? {
id: r.author.id ?? null,
firstName: r.author.firstName ?? null,
lastName: r.author.lastName ?? null,
email: r.author.email ?? null,
}
: null,
createdAt: r.createdAt ?? null,
noteId: r.id ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Created note UUID' },
content: { type: 'string', description: 'Note content as stored' },
author: {
type: 'object',
description: 'Note author',
optional: true,
properties: {
id: { type: 'string', description: 'Author user UUID' },
firstName: { type: 'string', description: 'First name' },
lastName: { type: 'string', description: 'Last name' },
email: { type: 'string', description: 'Email address' },
},
},
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
noteId: { type: 'string', description: 'Created note UUID' },
},
}
+1 -1
View File
@@ -30,7 +30,7 @@ export const getCandidateTool: ToolConfig<AshbyGetCandidateParams, AshbyGetCandi
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: (params) => ({
candidateId: params.candidateId,
candidateId: params.candidateId.trim(),
}),
},
+2 -2
View File
@@ -30,7 +30,7 @@ export const getJobTool: ToolConfig<AshbyGetJobParams, AshbyGetJobResponse> = {
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: (params) => ({
jobId: params.jobId,
jobId: params.jobId.trim(),
}),
},
@@ -63,7 +63,7 @@ export const getJobTool: ToolConfig<AshbyGetJobParams, AshbyGetJobResponse> = {
outputs: {
id: { type: 'string', description: 'Job UUID' },
title: { type: 'string', description: 'Job title' },
status: { type: 'string', description: 'Job status (Open, Closed, Draft, Archived, On Hold)' },
status: { type: 'string', description: 'Job status (Open, Closed, Draft, Archived)' },
employmentType: {
type: 'string',
description: 'Employment type (FullTime, PartTime, Intern, Contract, Temporary)',
+106
View File
@@ -0,0 +1,106 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyGetJobPostingParams {
apiKey: string
jobPostingId: string
}
interface AshbyGetJobPostingResponse extends ToolResponse {
output: {
id: string
title: string
jobId: string | null
locationName: string | null
departmentName: string | null
employmentType: string | null
descriptionPlain: string | null
isListed: boolean
publishedDate: string | null
externalLink: string | null
}
}
export const getJobPostingTool: ToolConfig<AshbyGetJobPostingParams, AshbyGetJobPostingResponse> = {
id: 'ashby_get_job_posting',
name: 'Ashby Get Job Posting',
description: 'Retrieves full details about a single job posting by its ID.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ashby API Key',
},
jobPostingId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The UUID of the job posting to fetch',
},
},
request: {
url: 'https://api.ashbyhq.com/jobPosting.info',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: (params) => ({
jobPostingId: params.jobPostingId,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.errorInfo?.message || 'Failed to get job posting')
}
const r = data.results
return {
success: true,
output: {
id: r.id ?? null,
title: r.jobTitle ?? r.title ?? null,
jobId: r.jobId ?? null,
locationName: r.locationName ?? null,
departmentName: r.departmentName ?? null,
employmentType: r.employmentType ?? null,
descriptionPlain: r.descriptionPlain ?? r.description ?? null,
isListed: r.isListed ?? false,
publishedDate: r.publishedDate ?? null,
externalLink: r.externalLink ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Job posting UUID' },
title: { type: 'string', description: 'Job posting title' },
jobId: { type: 'string', description: 'Associated job UUID', optional: true },
locationName: { type: 'string', description: 'Location name', optional: true },
departmentName: { type: 'string', description: 'Department name', optional: true },
employmentType: {
type: 'string',
description: 'Employment type (e.g. FullTime, PartTime, Contract)',
optional: true,
},
descriptionPlain: {
type: 'string',
description: 'Job posting description in plain text',
optional: true,
},
isListed: { type: 'boolean', description: 'Whether the posting is publicly listed' },
publishedDate: { type: 'string', description: 'ISO 8601 published date', optional: true },
externalLink: {
type: 'string',
description: 'External link to the job posting',
optional: true,
},
},
}
+116
View File
@@ -0,0 +1,116 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyGetOfferParams {
apiKey: string
offerId: string
}
interface AshbyGetOfferResponse extends ToolResponse {
output: {
id: string
offerStatus: string
acceptanceStatus: string | null
applicationId: string | null
startDate: string | null
salary: {
currencyCode: string
value: number
} | null
openingId: string | null
createdAt: string | null
}
}
export const getOfferTool: ToolConfig<AshbyGetOfferParams, AshbyGetOfferResponse> = {
id: 'ashby_get_offer',
name: 'Ashby Get Offer',
description: 'Retrieves full details about a single offer by its ID.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ashby API Key',
},
offerId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The UUID of the offer to fetch',
},
},
request: {
url: 'https://api.ashbyhq.com/offer.info',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: (params) => ({
offerId: params.offerId,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.errorInfo?.message || 'Failed to get offer')
}
const r = data.results
const v = r.latestVersion
return {
success: true,
output: {
id: r.id ?? null,
offerStatus: r.offerStatus ?? null,
acceptanceStatus: r.acceptanceStatus ?? null,
applicationId: r.applicationId ?? null,
startDate: v?.startDate ?? null,
salary: v?.salary
? {
currencyCode: v.salary.currencyCode ?? null,
value: v.salary.value ?? null,
}
: null,
openingId: v?.openingId ?? null,
createdAt: v?.createdAt ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Offer UUID' },
offerStatus: {
type: 'string',
description: 'Offer status (e.g. WaitingOnCandidateResponse, CandidateAccepted)',
},
acceptanceStatus: {
type: 'string',
description: 'Acceptance status (e.g. Accepted, Declined, Pending)',
optional: true,
},
applicationId: { type: 'string', description: 'Associated application UUID', optional: true },
startDate: { type: 'string', description: 'Offer start date', optional: true },
salary: {
type: 'object',
description: 'Salary details',
optional: true,
properties: {
currencyCode: { type: 'string', description: 'ISO 4217 currency code' },
value: { type: 'number', description: 'Salary amount' },
},
},
openingId: { type: 'string', description: 'Associated opening UUID', optional: true },
createdAt: {
type: 'string',
description: 'ISO 8601 creation timestamp (from latest version)',
optional: true,
},
},
}
+30
View File
@@ -1,28 +1,58 @@
import { addCandidateTagTool } from '@/tools/ashby/add_candidate_tag'
import { changeApplicationStageTool } from '@/tools/ashby/change_application_stage'
import { createApplicationTool } from '@/tools/ashby/create_application'
import { createCandidateTool } from '@/tools/ashby/create_candidate'
import { createNoteTool } from '@/tools/ashby/create_note'
import { getApplicationTool } from '@/tools/ashby/get_application'
import { getCandidateTool } from '@/tools/ashby/get_candidate'
import { getJobTool } from '@/tools/ashby/get_job'
import { getJobPostingTool } from '@/tools/ashby/get_job_posting'
import { getOfferTool } from '@/tools/ashby/get_offer'
import { listApplicationsTool } from '@/tools/ashby/list_applications'
import { listArchiveReasonsTool } from '@/tools/ashby/list_archive_reasons'
import { listCandidateTagsTool } from '@/tools/ashby/list_candidate_tags'
import { listCandidatesTool } from '@/tools/ashby/list_candidates'
import { listCustomFieldsTool } from '@/tools/ashby/list_custom_fields'
import { listDepartmentsTool } from '@/tools/ashby/list_departments'
import { listInterviewsTool } from '@/tools/ashby/list_interviews'
import { listJobPostingsTool } from '@/tools/ashby/list_job_postings'
import { listJobsTool } from '@/tools/ashby/list_jobs'
import { listLocationsTool } from '@/tools/ashby/list_locations'
import { listNotesTool } from '@/tools/ashby/list_notes'
import { listOffersTool } from '@/tools/ashby/list_offers'
import { listOpeningsTool } from '@/tools/ashby/list_openings'
import { listSourcesTool } from '@/tools/ashby/list_sources'
import { listUsersTool } from '@/tools/ashby/list_users'
import { removeCandidateTagTool } from '@/tools/ashby/remove_candidate_tag'
import { searchCandidatesTool } from '@/tools/ashby/search_candidates'
import { updateCandidateTool } from '@/tools/ashby/update_candidate'
export const ashbyAddCandidateTagTool = addCandidateTagTool
export const ashbyChangeApplicationStageTool = changeApplicationStageTool
export const ashbyCreateApplicationTool = createApplicationTool
export const ashbyCreateCandidateTool = createCandidateTool
export const ashbyCreateNoteTool = createNoteTool
export const ashbyGetApplicationTool = getApplicationTool
export const ashbyGetCandidateTool = getCandidateTool
export const ashbyGetJobTool = getJobTool
export const ashbyGetJobPostingTool = getJobPostingTool
export const ashbyGetOfferTool = getOfferTool
export const ashbyListApplicationsTool = listApplicationsTool
export const ashbyListArchiveReasonsTool = listArchiveReasonsTool
export const ashbyListCandidateTagsTool = listCandidateTagsTool
export const ashbyListCandidatesTool = listCandidatesTool
export const ashbyListCustomFieldsTool = listCustomFieldsTool
export const ashbyListDepartmentsTool = listDepartmentsTool
export const ashbyListInterviewsTool = listInterviewsTool
export const ashbyListJobPostingsTool = listJobPostingsTool
export const ashbyListJobsTool = listJobsTool
export const ashbyListLocationsTool = listLocationsTool
export const ashbyListNotesTool = listNotesTool
export const ashbyListOffersTool = listOffersTool
export const ashbyListOpeningsTool = listOpeningsTool
export const ashbyListSourcesTool = listSourcesTool
export const ashbyListUsersTool = listUsersTool
export const ashbyRemoveCandidateTagTool = removeCandidateTagTool
export const ashbySearchCandidatesTool = searchCandidatesTool
export const ashbyUpdateCandidateTool = updateCandidateTool
+1 -1
View File
@@ -71,7 +71,7 @@ export const listApplicationsTool: ToolConfig<
if (params.status) body.status = [params.status]
if (params.jobId) body.jobId = params.jobId
if (params.candidateId) body.candidateId = params.candidateId
if (params.createdAfter) body.createdAfter = params.createdAfter
if (params.createdAfter) body.createdAfter = new Date(params.createdAfter).getTime()
return body
},
},
@@ -0,0 +1,81 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyListArchiveReasonsParams {
apiKey: string
}
interface AshbyListArchiveReasonsResponse extends ToolResponse {
output: {
archiveReasons: Array<{
id: string
text: string
reasonType: string
isArchived: boolean
}>
}
}
export const listArchiveReasonsTool: ToolConfig<
AshbyListArchiveReasonsParams,
AshbyListArchiveReasonsResponse
> = {
id: 'ashby_list_archive_reasons',
name: 'Ashby List Archive Reasons',
description: 'Lists all archive reasons configured in Ashby.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ashby API Key',
},
},
request: {
url: 'https://api.ashbyhq.com/archiveReason.list',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: () => ({}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.errorInfo?.message || 'Failed to list archive reasons')
}
return {
success: true,
output: {
archiveReasons: (data.results ?? []).map((r: Record<string, unknown>) => ({
id: r.id ?? null,
text: r.text ?? null,
reasonType: r.reasonType ?? null,
isArchived: r.isArchived ?? false,
})),
},
}
},
outputs: {
archiveReasons: {
type: 'array',
description: 'List of archive reasons',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Archive reason UUID' },
text: { type: 'string', description: 'Archive reason text' },
reasonType: { type: 'string', description: 'Reason type' },
isArchived: { type: 'boolean', description: 'Whether the reason is archived' },
},
},
},
},
}
@@ -0,0 +1,78 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyListCandidateTagsParams {
apiKey: string
}
interface AshbyListCandidateTagsResponse extends ToolResponse {
output: {
tags: Array<{
id: string
title: string
isArchived: boolean
}>
}
}
export const listCandidateTagsTool: ToolConfig<
AshbyListCandidateTagsParams,
AshbyListCandidateTagsResponse
> = {
id: 'ashby_list_candidate_tags',
name: 'Ashby List Candidate Tags',
description: 'Lists all candidate tags configured in Ashby.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ashby API Key',
},
},
request: {
url: 'https://api.ashbyhq.com/candidateTag.list',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: () => ({}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.errorInfo?.message || 'Failed to list candidate tags')
}
return {
success: true,
output: {
tags: (data.results ?? []).map((t: Record<string, unknown>) => ({
id: t.id ?? null,
title: t.title ?? null,
isArchived: t.isArchived ?? false,
})),
},
}
},
outputs: {
tags: {
type: 'array',
description: 'List of candidate tags',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Tag UUID' },
title: { type: 'string', description: 'Tag title' },
isArchived: { type: 'boolean', description: 'Whether the tag is archived' },
},
},
},
},
}
@@ -0,0 +1,87 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyListCustomFieldsParams {
apiKey: string
}
interface AshbyListCustomFieldsResponse extends ToolResponse {
output: {
customFields: Array<{
id: string
title: string
fieldType: string
objectType: string
isArchived: boolean
}>
}
}
export const listCustomFieldsTool: ToolConfig<
AshbyListCustomFieldsParams,
AshbyListCustomFieldsResponse
> = {
id: 'ashby_list_custom_fields',
name: 'Ashby List Custom Fields',
description: 'Lists all custom field definitions configured in Ashby.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ashby API Key',
},
},
request: {
url: 'https://api.ashbyhq.com/customField.list',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: () => ({}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.errorInfo?.message || 'Failed to list custom fields')
}
return {
success: true,
output: {
customFields: (data.results ?? []).map((f: Record<string, unknown>) => ({
id: f.id ?? null,
title: f.title ?? null,
fieldType: f.fieldType ?? null,
objectType: f.objectType ?? null,
isArchived: f.isArchived ?? false,
})),
},
}
},
outputs: {
customFields: {
type: 'array',
description: 'List of custom field definitions',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Custom field UUID' },
title: { type: 'string', description: 'Custom field title' },
fieldType: { type: 'string', description: 'Field type (e.g. String, Number, Boolean)' },
objectType: {
type: 'string',
description: 'Object type the field applies to (e.g. Candidate, Application, Job)',
},
isArchived: { type: 'boolean', description: 'Whether the custom field is archived' },
},
},
},
},
}
+85
View File
@@ -0,0 +1,85 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyListDepartmentsParams {
apiKey: string
}
interface AshbyListDepartmentsResponse extends ToolResponse {
output: {
departments: Array<{
id: string
name: string
isArchived: boolean
parentId: string | null
}>
}
}
export const listDepartmentsTool: ToolConfig<
AshbyListDepartmentsParams,
AshbyListDepartmentsResponse
> = {
id: 'ashby_list_departments',
name: 'Ashby List Departments',
description: 'Lists all departments in Ashby.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ashby API Key',
},
},
request: {
url: 'https://api.ashbyhq.com/department.list',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: () => ({}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.errorInfo?.message || 'Failed to list departments')
}
return {
success: true,
output: {
departments: (data.results ?? []).map((d: Record<string, unknown>) => ({
id: d.id ?? null,
name: d.name ?? null,
isArchived: d.isArchived ?? false,
parentId: (d.parentId as string) ?? null,
})),
},
}
},
outputs: {
departments: {
type: 'array',
description: 'List of departments',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Department UUID' },
name: { type: 'string', description: 'Department name' },
isArchived: { type: 'boolean', description: 'Whether the department is archived' },
parentId: {
type: 'string',
description: 'Parent department UUID',
optional: true,
},
},
},
},
},
}
+137
View File
@@ -0,0 +1,137 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyListInterviewSchedulesParams {
apiKey: string
applicationId?: string
interviewStageId?: string
cursor?: string
perPage?: number
}
interface AshbyListInterviewSchedulesResponse extends ToolResponse {
output: {
interviewSchedules: Array<{
id: string
applicationId: string
interviewStageId: string | null
status: string | null
createdAt: string
}>
moreDataAvailable: boolean
nextCursor: string | null
}
}
export const listInterviewsTool: ToolConfig<
AshbyListInterviewSchedulesParams,
AshbyListInterviewSchedulesResponse
> = {
id: 'ashby_list_interviews',
name: 'Ashby List Interview Schedules',
description:
'Lists interview schedules in Ashby, optionally filtered by application or interview stage.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ashby API Key',
},
applicationId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'The UUID of the application to list interview schedules for',
},
interviewStageId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'The UUID of the interview stage to list interview schedules for',
},
cursor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Opaque pagination cursor from a previous response nextCursor value',
},
perPage: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (default 100)',
},
},
request: {
url: 'https://api.ashbyhq.com/interviewSchedule.list',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.applicationId) body.applicationId = params.applicationId
if (params.interviewStageId) body.interviewStageId = params.interviewStageId
if (params.cursor) body.cursor = params.cursor
if (params.perPage) body.limit = params.perPage
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.errorInfo?.message || 'Failed to list interview schedules')
}
return {
success: true,
output: {
interviewSchedules: (data.results ?? []).map((s: Record<string, unknown>) => ({
id: s.id ?? null,
applicationId: s.applicationId ?? null,
interviewStageId: s.interviewStageId ?? null,
status: s.status ?? null,
createdAt: s.createdAt ?? null,
})),
moreDataAvailable: data.moreDataAvailable ?? false,
nextCursor: data.nextCursor ?? null,
},
}
},
outputs: {
interviewSchedules: {
type: 'array',
description: 'List of interview schedules',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Interview schedule UUID' },
applicationId: { type: 'string', description: 'Associated application UUID' },
interviewStageId: {
type: 'string',
description: 'Interview stage UUID',
optional: true,
},
status: { type: 'string', description: 'Schedule status', optional: true },
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
},
},
},
moreDataAvailable: {
type: 'boolean',
description: 'Whether more pages of results exist',
},
nextCursor: {
type: 'string',
description: 'Opaque cursor for fetching the next page',
optional: true,
},
},
}
+97
View File
@@ -0,0 +1,97 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyListJobPostingsParams {
apiKey: string
}
interface AshbyListJobPostingsResponse extends ToolResponse {
output: {
jobPostings: Array<{
id: string
title: string
jobId: string | null
locationName: string | null
departmentName: string | null
employmentType: string | null
isListed: boolean
publishedDate: string | null
}>
}
}
export const listJobPostingsTool: ToolConfig<
AshbyListJobPostingsParams,
AshbyListJobPostingsResponse
> = {
id: 'ashby_list_job_postings',
name: 'Ashby List Job Postings',
description: 'Lists all job postings in Ashby.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ashby API Key',
},
},
request: {
url: 'https://api.ashbyhq.com/jobPosting.list',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: () => ({}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.errorInfo?.message || 'Failed to list job postings')
}
return {
success: true,
output: {
jobPostings: (data.results ?? []).map((jp: Record<string, unknown>) => ({
id: jp.id ?? null,
title: (jp.jobTitle as string) ?? (jp.title as string) ?? null,
jobId: jp.jobId ?? null,
locationName: jp.locationName ?? null,
departmentName: jp.departmentName ?? null,
employmentType: jp.employmentType ?? null,
isListed: jp.isListed ?? false,
publishedDate: jp.publishedDate ?? null,
})),
},
}
},
outputs: {
jobPostings: {
type: 'array',
description: 'List of job postings',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Job posting UUID' },
title: { type: 'string', description: 'Job posting title' },
jobId: { type: 'string', description: 'Associated job UUID', optional: true },
locationName: { type: 'string', description: 'Location name', optional: true },
departmentName: { type: 'string', description: 'Department name', optional: true },
employmentType: {
type: 'string',
description: 'Employment type (e.g. FullTime, PartTime, Contract)',
optional: true,
},
isListed: { type: 'boolean', description: 'Whether the posting is publicly listed' },
publishedDate: { type: 'string', description: 'ISO 8601 published date', optional: true },
},
},
},
},
}
+112
View File
@@ -0,0 +1,112 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyListLocationsParams {
apiKey: string
}
interface AshbyListLocationsResponse extends ToolResponse {
output: {
locations: Array<{
id: string
name: string
isArchived: boolean
isRemote: boolean
address: {
city: string | null
region: string | null
country: string | null
} | null
}>
}
}
export const listLocationsTool: ToolConfig<AshbyListLocationsParams, AshbyListLocationsResponse> = {
id: 'ashby_list_locations',
name: 'Ashby List Locations',
description: 'Lists all locations configured in Ashby.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ashby API Key',
},
},
request: {
url: 'https://api.ashbyhq.com/location.list',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: () => ({}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.errorInfo?.message || 'Failed to list locations')
}
return {
success: true,
output: {
locations: (data.results ?? []).map(
(
l: Record<string, unknown> & {
address?: {
postalAddress?: {
addressLocality?: string
addressRegion?: string
addressCountry?: string
}
}
}
) => ({
id: l.id ?? null,
name: l.name ?? null,
isArchived: l.isArchived ?? false,
isRemote: l.isRemote ?? false,
address: l.address?.postalAddress
? {
city: l.address.postalAddress.addressLocality ?? null,
region: l.address.postalAddress.addressRegion ?? null,
country: l.address.postalAddress.addressCountry ?? null,
}
: null,
})
),
},
}
},
outputs: {
locations: {
type: 'array',
description: 'List of locations',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Location UUID' },
name: { type: 'string', description: 'Location name' },
isArchived: { type: 'boolean', description: 'Whether the location is archived' },
isRemote: { type: 'boolean', description: 'Whether this is a remote location' },
address: {
type: 'object',
description: 'Location address',
optional: true,
properties: {
city: { type: 'string', description: 'City', optional: true },
region: { type: 'string', description: 'State or region', optional: true },
country: { type: 'string', description: 'Country', optional: true },
},
},
},
},
},
},
}
+47 -46
View File
@@ -10,17 +10,16 @@ interface AshbyListOffersResponse extends ToolResponse {
output: {
offers: Array<{
id: string
status: string
candidate: {
id: string
name: string
offerStatus: string
acceptanceStatus: string | null
applicationId: string | null
startDate: string | null
salary: {
currencyCode: string
value: number
} | null
job: {
id: string
title: string
} | null
createdAt: string
updatedAt: string
openingId: string | null
createdAt: string | null
}>
moreDataAvailable: boolean
nextCursor: string | null
@@ -82,27 +81,31 @@ export const listOffersTool: ToolConfig<AshbyListOffersParams, AshbyListOffersRe
offers: (data.results ?? []).map(
(
o: Record<string, unknown> & {
candidate?: { id?: string; name?: string }
job?: { id?: string; title?: string }
latestVersion?: {
startDate?: string
salary?: { currencyCode?: string; value?: number }
openingId?: string
createdAt?: string
}
}
) => ({
id: o.id ?? null,
status: o.status ?? o.offerStatus ?? null,
candidate: o.candidate
? {
id: o.candidate.id ?? null,
name: o.candidate.name ?? null,
}
: null,
job: o.job
? {
id: o.job.id ?? null,
title: o.job.title ?? null,
}
: null,
createdAt: o.createdAt ?? null,
updatedAt: o.updatedAt ?? null,
})
) => {
const v = o.latestVersion
return {
id: o.id ?? null,
offerStatus: o.offerStatus ?? null,
acceptanceStatus: o.acceptanceStatus ?? null,
applicationId: o.applicationId ?? null,
startDate: v?.startDate ?? null,
salary: v?.salary
? {
currencyCode: v.salary.currencyCode ?? null,
value: v.salary.value ?? null,
}
: null,
openingId: v?.openingId ?? null,
createdAt: v?.createdAt ?? null,
}
}
),
moreDataAvailable: data.moreDataAvailable ?? false,
nextCursor: data.nextCursor ?? null,
@@ -118,27 +121,25 @@ export const listOffersTool: ToolConfig<AshbyListOffersParams, AshbyListOffersRe
type: 'object',
properties: {
id: { type: 'string', description: 'Offer UUID' },
status: { type: 'string', description: 'Offer status' },
candidate: {
offerStatus: { type: 'string', description: 'Offer status' },
acceptanceStatus: { type: 'string', description: 'Acceptance status', optional: true },
applicationId: {
type: 'string',
description: 'Associated application UUID',
optional: true,
},
startDate: { type: 'string', description: 'Offer start date', optional: true },
salary: {
type: 'object',
description: 'Associated candidate',
description: 'Salary details',
optional: true,
properties: {
id: { type: 'string', description: 'Candidate UUID' },
name: { type: 'string', description: 'Candidate name' },
currencyCode: { type: 'string', description: 'ISO 4217 currency code' },
value: { type: 'number', description: 'Salary amount' },
},
},
job: {
type: 'object',
description: 'Associated job',
optional: true,
properties: {
id: { type: 'string', description: 'Job UUID' },
title: { type: 'string', description: 'Job title' },
},
},
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
updatedAt: { type: 'string', description: 'ISO 8601 last update timestamp' },
openingId: { type: 'string', description: 'Associated opening UUID', optional: true },
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp', optional: true },
},
},
},
+117
View File
@@ -0,0 +1,117 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyListOpeningsParams {
apiKey: string
cursor?: string
perPage?: number
}
interface AshbyListOpeningsResponse extends ToolResponse {
output: {
openings: Array<{
id: string
openingState: string | null
isArchived: boolean
openedAt: string | null
closedAt: string | null
}>
moreDataAvailable: boolean
nextCursor: string | null
}
}
export const listOpeningsTool: ToolConfig<AshbyListOpeningsParams, AshbyListOpeningsResponse> = {
id: 'ashby_list_openings',
name: 'Ashby List Openings',
description: 'Lists all openings in Ashby with pagination.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ashby API Key',
},
cursor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Opaque pagination cursor from a previous response nextCursor value',
},
perPage: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (default 100)',
},
},
request: {
url: 'https://api.ashbyhq.com/opening.list',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.cursor) body.cursor = params.cursor
if (params.perPage) body.limit = params.perPage
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.errorInfo?.message || 'Failed to list openings')
}
return {
success: true,
output: {
openings: (data.results ?? []).map((o: Record<string, unknown>) => ({
id: o.id ?? null,
openingState: o.openingState ?? null,
isArchived: o.isArchived ?? false,
openedAt: o.openedAt ?? null,
closedAt: o.closedAt ?? null,
})),
moreDataAvailable: data.moreDataAvailable ?? false,
nextCursor: data.nextCursor ?? null,
},
}
},
outputs: {
openings: {
type: 'array',
description: 'List of openings',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Opening UUID' },
openingState: {
type: 'string',
description: 'Opening state (Approved, Closed, Draft, Filled, Open)',
optional: true,
},
isArchived: { type: 'boolean', description: 'Whether the opening is archived' },
openedAt: { type: 'string', description: 'ISO 8601 opened timestamp', optional: true },
closedAt: { type: 'string', description: 'ISO 8601 closed timestamp', optional: true },
},
},
},
moreDataAvailable: {
type: 'boolean',
description: 'Whether more pages of results exist',
},
nextCursor: {
type: 'string',
description: 'Opaque cursor for fetching the next page',
optional: true,
},
},
}
+75
View File
@@ -0,0 +1,75 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyListSourcesParams {
apiKey: string
}
interface AshbyListSourcesResponse extends ToolResponse {
output: {
sources: Array<{
id: string
title: string
isArchived: boolean
}>
}
}
export const listSourcesTool: ToolConfig<AshbyListSourcesParams, AshbyListSourcesResponse> = {
id: 'ashby_list_sources',
name: 'Ashby List Sources',
description: 'Lists all candidate sources configured in Ashby.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ashby API Key',
},
},
request: {
url: 'https://api.ashbyhq.com/source.list',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: () => ({}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.errorInfo?.message || 'Failed to list sources')
}
return {
success: true,
output: {
sources: (data.results ?? []).map((s: Record<string, unknown>) => ({
id: s.id ?? null,
title: s.title ?? null,
isArchived: s.isArchived ?? false,
})),
},
}
},
outputs: {
sources: {
type: 'array',
description: 'List of sources',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Source UUID' },
title: { type: 'string', description: 'Source title' },
isArchived: { type: 'boolean', description: 'Whether the source is archived' },
},
},
},
},
}
+121
View File
@@ -0,0 +1,121 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyListUsersParams {
apiKey: string
cursor?: string
perPage?: number
}
interface AshbyListUsersResponse extends ToolResponse {
output: {
users: Array<{
id: string
firstName: string
lastName: string
email: string
isEnabled: boolean
globalRole: string | null
}>
moreDataAvailable: boolean
nextCursor: string | null
}
}
export const listUsersTool: ToolConfig<AshbyListUsersParams, AshbyListUsersResponse> = {
id: 'ashby_list_users',
name: 'Ashby List Users',
description: 'Lists all users in Ashby with pagination.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ashby API Key',
},
cursor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Opaque pagination cursor from a previous response nextCursor value',
},
perPage: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (default 100)',
},
},
request: {
url: 'https://api.ashbyhq.com/user.list',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.cursor) body.cursor = params.cursor
if (params.perPage) body.limit = params.perPage
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.errorInfo?.message || 'Failed to list users')
}
return {
success: true,
output: {
users: (data.results ?? []).map((u: Record<string, unknown>) => ({
id: u.id ?? null,
firstName: u.firstName ?? null,
lastName: u.lastName ?? null,
email: u.email ?? null,
isEnabled: u.isEnabled ?? false,
globalRole: u.globalRole ?? null,
})),
moreDataAvailable: data.moreDataAvailable ?? false,
nextCursor: data.nextCursor ?? null,
},
}
},
outputs: {
users: {
type: 'array',
description: 'List of users',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'User UUID' },
firstName: { type: 'string', description: 'First name' },
lastName: { type: 'string', description: 'Last name' },
email: { type: 'string', description: 'Email address' },
isEnabled: { type: 'boolean', description: 'Whether the user account is enabled' },
globalRole: {
type: 'string',
description:
'User role (Organization Admin, Elevated Access, Limited Access, External Recruiter)',
optional: true,
},
},
},
},
moreDataAvailable: {
type: 'boolean',
description: 'Whether more pages of results exist',
},
nextCursor: {
type: 'string',
description: 'Opaque cursor for fetching the next page',
optional: true,
},
},
}
@@ -0,0 +1,76 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyRemoveCandidateTagParams {
apiKey: string
candidateId: string
tagId: string
}
interface AshbyRemoveCandidateTagResponse extends ToolResponse {
output: {
success: boolean
}
}
export const removeCandidateTagTool: ToolConfig<
AshbyRemoveCandidateTagParams,
AshbyRemoveCandidateTagResponse
> = {
id: 'ashby_remove_candidate_tag',
name: 'Ashby Remove Candidate Tag',
description: 'Removes a tag from a candidate in Ashby.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ashby API Key',
},
candidateId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The UUID of the candidate to remove the tag from',
},
tagId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The UUID of the tag to remove',
},
},
request: {
url: 'https://api.ashbyhq.com/candidate.removeTag',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: (params) => ({
candidateId: params.candidateId,
tagId: params.tagId,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.errorInfo?.message || 'Failed to remove tag from candidate')
}
return {
success: true,
output: {
success: true,
},
}
},
outputs: {
success: { type: 'boolean', description: 'Whether the tag was successfully removed' },
},
}
@@ -80,6 +80,8 @@ export const searchCandidatesTool: ToolConfig<
isPrimary: c.primaryPhoneNumber.isPrimary ?? true,
}
: null,
createdAt: c.createdAt ?? null,
updatedAt: c.updatedAt ?? null,
})
),
},
@@ -115,6 +117,8 @@ export const searchCandidatesTool: ToolConfig<
isPrimary: { type: 'boolean', description: 'Whether this is the primary phone' },
},
},
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
updatedAt: { type: 'string', description: 'ISO 8601 last update timestamp' },
},
},
},
+4 -12
View File
@@ -21,10 +21,8 @@ export interface AshbyGetCandidateParams extends AshbyBaseParams {
export interface AshbyCreateCandidateParams extends AshbyBaseParams {
name: string
email?: string
emailType?: string
email: string
phoneNumber?: string
phoneType?: string
linkedInUrl?: string
githubUrl?: string
sourceId?: string
@@ -111,6 +109,8 @@ export interface AshbySearchCandidatesResponse extends ToolResponse {
name: string
primaryEmailAddress: AshbyContactInfo | null
primaryPhoneNumber: AshbyContactInfo | null
createdAt: string
updatedAt: string
}>
}
}
@@ -149,15 +149,7 @@ export interface AshbyGetJobResponse extends ToolResponse {
export interface AshbyCreateNoteResponse extends ToolResponse {
output: {
id: string
content: string
author: {
id: string
firstName: string
lastName: string
email: string
} | null
createdAt: string
noteId: string
}
}
+5 -35
View File
@@ -6,9 +6,7 @@ interface AshbyUpdateCandidateParams {
candidateId: string
name?: string
email?: string
emailType?: string
phoneNumber?: string
phoneType?: string
linkedInUrl?: string
githubUrl?: string
websiteUrl?: string
@@ -49,24 +47,12 @@ export const updateCandidateTool: ToolConfig<
visibility: 'user-or-llm',
description: 'Updated primary email address',
},
emailType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Email address type: Personal, Work, or Other (default Work)',
},
phoneNumber: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated primary phone number',
},
phoneType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Phone number type: Personal, Work, or Other (default Work)',
},
linkedInUrl: {
type: 'string',
required: false,
@@ -105,27 +91,11 @@ export const updateCandidateTool: ToolConfig<
candidateId: params.candidateId,
}
if (params.name) body.name = params.name
if (params.email) {
body.primaryEmailAddress = {
value: params.email,
type: params.emailType || 'Work',
isPrimary: true,
}
}
if (params.phoneNumber) {
body.primaryPhoneNumber = {
value: params.phoneNumber,
type: params.phoneType || 'Work',
isPrimary: true,
}
}
if (params.linkedInUrl || params.githubUrl || params.websiteUrl) {
const socialLinks: Array<{ url: string; type: string }> = []
if (params.linkedInUrl) socialLinks.push({ url: params.linkedInUrl, type: 'LinkedIn' })
if (params.githubUrl) socialLinks.push({ url: params.githubUrl, type: 'GitHub' })
if (params.websiteUrl) socialLinks.push({ url: params.websiteUrl, type: 'Website' })
body.socialLinks = socialLinks
}
if (params.email) body.email = params.email
if (params.phoneNumber) body.phoneNumber = params.phoneNumber
if (params.linkedInUrl) body.linkedInUrl = params.linkedInUrl
if (params.githubUrl) body.githubUrl = params.githubUrl
if (params.websiteUrl) body.websiteUrl = params.websiteUrl
if (params.sourceId) body.sourceId = params.sourceId
return body
},
+30
View File
@@ -97,17 +97,32 @@ import {
asanaUpdateTaskTool,
} from '@/tools/asana'
import {
ashbyAddCandidateTagTool,
ashbyChangeApplicationStageTool,
ashbyCreateApplicationTool,
ashbyCreateCandidateTool,
ashbyCreateNoteTool,
ashbyGetApplicationTool,
ashbyGetCandidateTool,
ashbyGetJobPostingTool,
ashbyGetJobTool,
ashbyGetOfferTool,
ashbyListApplicationsTool,
ashbyListArchiveReasonsTool,
ashbyListCandidatesTool,
ashbyListCandidateTagsTool,
ashbyListCustomFieldsTool,
ashbyListDepartmentsTool,
ashbyListInterviewsTool,
ashbyListJobPostingsTool,
ashbyListJobsTool,
ashbyListLocationsTool,
ashbyListNotesTool,
ashbyListOffersTool,
ashbyListOpeningsTool,
ashbyListSourcesTool,
ashbyListUsersTool,
ashbyRemoveCandidateTagTool,
ashbySearchCandidatesTool,
ashbyUpdateCandidateTool,
} from '@/tools/ashby'
@@ -2394,17 +2409,32 @@ export const tools: Record<string, ToolConfig> = {
asana_get_task: asanaGetTaskTool,
asana_search_tasks: asanaSearchTasksTool,
asana_update_task: asanaUpdateTaskTool,
ashby_add_candidate_tag: ashbyAddCandidateTagTool,
ashby_change_application_stage: ashbyChangeApplicationStageTool,
ashby_create_application: ashbyCreateApplicationTool,
ashby_create_candidate: ashbyCreateCandidateTool,
ashby_create_note: ashbyCreateNoteTool,
ashby_get_application: ashbyGetApplicationTool,
ashby_get_candidate: ashbyGetCandidateTool,
ashby_get_job: ashbyGetJobTool,
ashby_get_job_posting: ashbyGetJobPostingTool,
ashby_get_offer: ashbyGetOfferTool,
ashby_list_applications: ashbyListApplicationsTool,
ashby_list_archive_reasons: ashbyListArchiveReasonsTool,
ashby_list_candidate_tags: ashbyListCandidateTagsTool,
ashby_list_candidates: ashbyListCandidatesTool,
ashby_list_custom_fields: ashbyListCustomFieldsTool,
ashby_list_departments: ashbyListDepartmentsTool,
ashby_list_interviews: ashbyListInterviewsTool,
ashby_list_job_postings: ashbyListJobPostingsTool,
ashby_list_jobs: ashbyListJobsTool,
ashby_list_locations: ashbyListLocationsTool,
ashby_list_notes: ashbyListNotesTool,
ashby_list_offers: ashbyListOffersTool,
ashby_list_openings: ashbyListOpeningsTool,
ashby_list_sources: ashbyListSourcesTool,
ashby_list_users: ashbyListUsersTool,
ashby_remove_candidate_tag: ashbyRemoveCandidateTagTool,
ashby_search_candidates: ashbySearchCandidatesTool,
ashby_update_candidate: ashbyUpdateCandidateTool,
brandfetch_get_brand: brandfetchGetBrandTool,