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,228 @@
|
||||
import type {
|
||||
GreenhouseGetApplicationParams,
|
||||
GreenhouseGetApplicationResponse,
|
||||
} from '@/tools/greenhouse/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const greenhouseGetApplicationTool: ToolConfig<
|
||||
GreenhouseGetApplicationParams,
|
||||
GreenhouseGetApplicationResponse
|
||||
> = {
|
||||
id: 'greenhouse_get_application',
|
||||
name: 'Greenhouse Get Application',
|
||||
description:
|
||||
'Retrieves a specific application by ID with full details including source, stage, answers, and attachments',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Greenhouse Harvest API key',
|
||||
},
|
||||
applicationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the application to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GreenhouseGetApplicationParams) =>
|
||||
`https://harvest.greenhouse.io/v1/applications/${params.applicationId.trim()}`,
|
||||
method: 'GET',
|
||||
headers: (params: GreenhouseGetApplicationParams) => ({
|
||||
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response): Promise<GreenhouseGetApplicationResponse> => {
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
id: 0,
|
||||
candidate_id: 0,
|
||||
prospect: false,
|
||||
status: null,
|
||||
applied_at: null,
|
||||
rejected_at: null,
|
||||
last_activity_at: null,
|
||||
location: null,
|
||||
source: null,
|
||||
credited_to: null,
|
||||
recruiter: null,
|
||||
coordinator: null,
|
||||
current_stage: null,
|
||||
rejection_reason: null,
|
||||
jobs: [],
|
||||
job_post_id: null,
|
||||
answers: [],
|
||||
attachments: [],
|
||||
custom_fields: {},
|
||||
},
|
||||
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
|
||||
}
|
||||
}
|
||||
|
||||
const a = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: a.id ?? 0,
|
||||
candidate_id: a.candidate_id ?? 0,
|
||||
prospect: a.prospect ?? false,
|
||||
status: a.status ?? null,
|
||||
applied_at: a.applied_at ?? null,
|
||||
rejected_at: a.rejected_at ?? null,
|
||||
last_activity_at: a.last_activity_at ?? null,
|
||||
location: a.location ?? null,
|
||||
source: a.source ?? null,
|
||||
credited_to: a.credited_to ?? null,
|
||||
recruiter: a.recruiter ?? null,
|
||||
coordinator: a.coordinator ?? null,
|
||||
current_stage: a.current_stage ?? null,
|
||||
rejection_reason: a.rejection_reason ?? null,
|
||||
jobs: a.jobs ?? [],
|
||||
job_post_id: a.job_post_id ?? null,
|
||||
answers: a.answers ?? [],
|
||||
attachments: (a.attachments ?? []).map((att: Record<string, unknown>) => ({
|
||||
filename: att.filename ?? '',
|
||||
url: att.url ?? '',
|
||||
type: att.type ?? '',
|
||||
created_at: (att.created_at as string) ?? null,
|
||||
})),
|
||||
custom_fields: a.custom_fields ?? {},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'number', description: 'Application ID' },
|
||||
candidate_id: { type: 'number', description: 'Associated candidate ID' },
|
||||
prospect: { type: 'boolean', description: 'Whether this is a prospect application' },
|
||||
status: { type: 'string', description: 'Status (active, converted, hired, rejected)' },
|
||||
applied_at: { type: 'string', description: 'Application date (ISO 8601)' },
|
||||
rejected_at: { type: 'string', description: 'Rejection date (ISO 8601)', optional: true },
|
||||
last_activity_at: { type: 'string', description: 'Last activity date (ISO 8601)' },
|
||||
location: {
|
||||
type: 'object',
|
||||
description: 'Candidate location',
|
||||
optional: true,
|
||||
properties: {
|
||||
address: { type: 'string', description: 'Location address', optional: true },
|
||||
},
|
||||
},
|
||||
source: {
|
||||
type: 'object',
|
||||
description: 'Application source',
|
||||
optional: true,
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Source ID' },
|
||||
public_name: { type: 'string', description: 'Source name' },
|
||||
},
|
||||
},
|
||||
credited_to: {
|
||||
type: 'object',
|
||||
description: 'User credited for the application',
|
||||
optional: true,
|
||||
properties: {
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
first_name: { type: 'string', description: 'First name' },
|
||||
last_name: { type: 'string', description: 'Last name' },
|
||||
name: { type: 'string', description: 'Full name' },
|
||||
employee_id: { type: 'string', description: 'Employee ID', optional: true },
|
||||
},
|
||||
},
|
||||
recruiter: {
|
||||
type: 'object',
|
||||
description: 'Assigned recruiter',
|
||||
optional: true,
|
||||
properties: {
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
first_name: { type: 'string', description: 'First name' },
|
||||
last_name: { type: 'string', description: 'Last name' },
|
||||
name: { type: 'string', description: 'Full name' },
|
||||
employee_id: { type: 'string', description: 'Employee ID', optional: true },
|
||||
},
|
||||
},
|
||||
coordinator: {
|
||||
type: 'object',
|
||||
description: 'Assigned coordinator',
|
||||
optional: true,
|
||||
properties: {
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
first_name: { type: 'string', description: 'First name' },
|
||||
last_name: { type: 'string', description: 'Last name' },
|
||||
name: { type: 'string', description: 'Full name' },
|
||||
employee_id: { type: 'string', description: 'Employee ID', optional: true },
|
||||
},
|
||||
},
|
||||
current_stage: {
|
||||
type: 'object',
|
||||
description: 'Current interview stage (null when hired)',
|
||||
optional: true,
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Stage ID' },
|
||||
name: { type: 'string', description: 'Stage name' },
|
||||
},
|
||||
},
|
||||
rejection_reason: {
|
||||
type: 'object',
|
||||
description: 'Rejection reason',
|
||||
optional: true,
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Rejection reason ID' },
|
||||
name: { type: 'string', description: 'Rejection reason name' },
|
||||
type: {
|
||||
type: 'object',
|
||||
description: 'Rejection reason type',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Type ID' },
|
||||
name: { type: 'string', description: 'Type name' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
jobs: {
|
||||
type: 'array',
|
||||
description: 'Associated jobs',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Job ID' },
|
||||
name: { type: 'string', description: 'Job name' },
|
||||
},
|
||||
},
|
||||
},
|
||||
job_post_id: { type: 'number', description: 'Job post ID', optional: true },
|
||||
answers: {
|
||||
type: 'array',
|
||||
description: 'Application question answers',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
question: { type: 'string', description: 'Question text' },
|
||||
answer: { type: 'string', description: 'Answer text' },
|
||||
},
|
||||
},
|
||||
},
|
||||
attachments: {
|
||||
type: 'array',
|
||||
description: 'File attachments (URLs expire after 7 days)',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
filename: { type: 'string', description: 'File name' },
|
||||
url: { type: 'string', description: 'Download URL (expires after 7 days)' },
|
||||
type: { type: 'string', description: 'Type (resume, cover_letter, offer_packet, other)' },
|
||||
created_at: { type: 'string', description: 'Upload timestamp', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
custom_fields: { type: 'object', description: 'Custom field values' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
import type {
|
||||
GreenhouseGetCandidateParams,
|
||||
GreenhouseGetCandidateResponse,
|
||||
} from '@/tools/greenhouse/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const greenhouseGetCandidateTool: ToolConfig<
|
||||
GreenhouseGetCandidateParams,
|
||||
GreenhouseGetCandidateResponse
|
||||
> = {
|
||||
id: 'greenhouse_get_candidate',
|
||||
name: 'Greenhouse Get Candidate',
|
||||
description:
|
||||
'Retrieves a specific candidate by ID with full details including contact info, education, and employment history',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Greenhouse Harvest API key',
|
||||
},
|
||||
candidateId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the candidate to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GreenhouseGetCandidateParams) =>
|
||||
`https://harvest.greenhouse.io/v1/candidates/${params.candidateId.trim()}`,
|
||||
method: 'GET',
|
||||
headers: (params: GreenhouseGetCandidateParams) => ({
|
||||
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response): Promise<GreenhouseGetCandidateResponse> => {
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
id: 0,
|
||||
first_name: null,
|
||||
last_name: null,
|
||||
company: null,
|
||||
title: null,
|
||||
is_private: false,
|
||||
can_email: false,
|
||||
created_at: null,
|
||||
updated_at: null,
|
||||
last_activity: null,
|
||||
email_addresses: [],
|
||||
phone_numbers: [],
|
||||
addresses: [],
|
||||
website_addresses: [],
|
||||
social_media_addresses: [],
|
||||
tags: [],
|
||||
application_ids: [],
|
||||
recruiter: null,
|
||||
coordinator: null,
|
||||
attachments: [],
|
||||
educations: [],
|
||||
employments: [],
|
||||
custom_fields: {},
|
||||
},
|
||||
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
|
||||
}
|
||||
}
|
||||
|
||||
const c = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: c.id ?? 0,
|
||||
first_name: c.first_name ?? null,
|
||||
last_name: c.last_name ?? null,
|
||||
company: c.company ?? null,
|
||||
title: c.title ?? null,
|
||||
is_private: c.is_private ?? false,
|
||||
can_email: c.can_email ?? false,
|
||||
created_at: c.created_at ?? null,
|
||||
updated_at: c.updated_at ?? null,
|
||||
last_activity: c.last_activity ?? null,
|
||||
email_addresses: c.email_addresses ?? [],
|
||||
phone_numbers: c.phone_numbers ?? [],
|
||||
addresses: c.addresses ?? [],
|
||||
website_addresses: c.website_addresses ?? [],
|
||||
social_media_addresses: c.social_media_addresses ?? [],
|
||||
tags: c.tags ?? [],
|
||||
application_ids: c.application_ids ?? [],
|
||||
recruiter: c.recruiter ?? null,
|
||||
coordinator: c.coordinator ?? null,
|
||||
attachments: (c.attachments ?? []).map((a: Record<string, unknown>) => ({
|
||||
filename: a.filename ?? '',
|
||||
url: a.url ?? '',
|
||||
type: a.type ?? '',
|
||||
created_at: (a.created_at as string) ?? null,
|
||||
})),
|
||||
educations: (c.educations ?? []).map((e: Record<string, unknown>) => ({
|
||||
id: e.id ?? 0,
|
||||
school_name: e.school_name ?? null,
|
||||
degree: e.degree ?? null,
|
||||
discipline: e.discipline ?? null,
|
||||
start_date: (e.start_date as string) ?? null,
|
||||
end_date: (e.end_date as string) ?? null,
|
||||
})),
|
||||
employments: (c.employments ?? []).map((e: Record<string, unknown>) => ({
|
||||
id: e.id ?? 0,
|
||||
company_name: e.company_name ?? null,
|
||||
title: e.title ?? null,
|
||||
start_date: (e.start_date as string) ?? null,
|
||||
end_date: (e.end_date as string) ?? null,
|
||||
})),
|
||||
custom_fields: c.custom_fields ?? {},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'number', description: 'Candidate ID' },
|
||||
first_name: { type: 'string', description: 'First name' },
|
||||
last_name: { type: 'string', description: 'Last name' },
|
||||
company: { type: 'string', description: 'Current employer', optional: true },
|
||||
title: { type: 'string', description: 'Current job title', optional: true },
|
||||
is_private: { type: 'boolean', description: 'Whether candidate is private' },
|
||||
can_email: { type: 'boolean', description: 'Whether candidate can be emailed' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
|
||||
updated_at: { type: 'string', description: 'Last updated timestamp (ISO 8601)' },
|
||||
last_activity: {
|
||||
type: 'string',
|
||||
description: 'Last activity timestamp (ISO 8601)',
|
||||
optional: true,
|
||||
},
|
||||
email_addresses: {
|
||||
type: 'array',
|
||||
description: 'Email addresses',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
value: { type: 'string', description: 'Email address' },
|
||||
type: { type: 'string', description: 'Type (personal, work, other)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
phone_numbers: {
|
||||
type: 'array',
|
||||
description: 'Phone numbers',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
value: { type: 'string', description: 'Phone number' },
|
||||
type: { type: 'string', description: 'Type (home, work, mobile, skype, other)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
addresses: {
|
||||
type: 'array',
|
||||
description: 'Addresses',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
value: { type: 'string', description: 'Address' },
|
||||
type: { type: 'string', description: 'Type (home, work, other)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
website_addresses: {
|
||||
type: 'array',
|
||||
description: 'Website addresses',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
value: { type: 'string', description: 'URL' },
|
||||
type: { type: 'string', description: 'Type (personal, company, portfolio, blog, other)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
social_media_addresses: {
|
||||
type: 'array',
|
||||
description: 'Social media profiles',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
value: { type: 'string', description: 'URL or handle' },
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: { type: 'array', description: 'Tags', items: { type: 'string', description: 'Tag' } },
|
||||
application_ids: {
|
||||
type: 'array',
|
||||
description: 'Associated application IDs',
|
||||
items: { type: 'number', description: 'Application ID' },
|
||||
},
|
||||
recruiter: {
|
||||
type: 'object',
|
||||
description: 'Assigned recruiter',
|
||||
optional: true,
|
||||
properties: {
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
first_name: { type: 'string', description: 'First name' },
|
||||
last_name: { type: 'string', description: 'Last name' },
|
||||
name: { type: 'string', description: 'Full name' },
|
||||
employee_id: { type: 'string', description: 'Employee ID', optional: true },
|
||||
},
|
||||
},
|
||||
coordinator: {
|
||||
type: 'object',
|
||||
description: 'Assigned coordinator',
|
||||
optional: true,
|
||||
properties: {
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
first_name: { type: 'string', description: 'First name' },
|
||||
last_name: { type: 'string', description: 'Last name' },
|
||||
name: { type: 'string', description: 'Full name' },
|
||||
employee_id: { type: 'string', description: 'Employee ID', optional: true },
|
||||
},
|
||||
},
|
||||
attachments: {
|
||||
type: 'array',
|
||||
description: 'File attachments (URLs expire after 7 days)',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
filename: { type: 'string', description: 'File name' },
|
||||
url: { type: 'string', description: 'Download URL (expires after 7 days)' },
|
||||
type: { type: 'string', description: 'Type (resume, cover_letter, offer_packet, other)' },
|
||||
created_at: { type: 'string', description: 'Upload timestamp', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
educations: {
|
||||
type: 'array',
|
||||
description: 'Education history',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Education record ID' },
|
||||
school_name: { type: 'string', description: 'School name', optional: true },
|
||||
degree: { type: 'string', description: 'Degree type', optional: true },
|
||||
discipline: { type: 'string', description: 'Field of study', optional: true },
|
||||
start_date: { type: 'string', description: 'Start date (ISO 8601)', optional: true },
|
||||
end_date: { type: 'string', description: 'End date (ISO 8601)', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
employments: {
|
||||
type: 'array',
|
||||
description: 'Employment history',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Employment record ID' },
|
||||
company_name: { type: 'string', description: 'Company name', optional: true },
|
||||
title: { type: 'string', description: 'Job title', optional: true },
|
||||
start_date: { type: 'string', description: 'Start date (ISO 8601)', optional: true },
|
||||
end_date: { type: 'string', description: 'End date (ISO 8601)', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
custom_fields: { type: 'object', description: 'Custom field values' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
import type { GreenhouseGetJobParams, GreenhouseGetJobResponse } from '@/tools/greenhouse/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const greenhouseGetJobTool: ToolConfig<GreenhouseGetJobParams, GreenhouseGetJobResponse> = {
|
||||
id: 'greenhouse_get_job',
|
||||
name: 'Greenhouse Get Job',
|
||||
description:
|
||||
'Retrieves a specific job by ID with full details including hiring team, openings, and custom fields',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Greenhouse Harvest API key',
|
||||
},
|
||||
jobId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the job to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GreenhouseGetJobParams) =>
|
||||
`https://harvest.greenhouse.io/v1/jobs/${params.jobId.trim()}`,
|
||||
method: 'GET',
|
||||
headers: (params: GreenhouseGetJobParams) => ({
|
||||
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response): Promise<GreenhouseGetJobResponse> => {
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
id: 0,
|
||||
name: null,
|
||||
requisition_id: null,
|
||||
status: null,
|
||||
confidential: false,
|
||||
created_at: null,
|
||||
opened_at: null,
|
||||
closed_at: null,
|
||||
updated_at: null,
|
||||
is_template: null,
|
||||
notes: null,
|
||||
departments: [],
|
||||
offices: [],
|
||||
hiring_team: { hiring_managers: [], recruiters: [], coordinators: [], sourcers: [] },
|
||||
openings: [],
|
||||
custom_fields: {},
|
||||
},
|
||||
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
|
||||
}
|
||||
}
|
||||
|
||||
const j = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: j.id ?? 0,
|
||||
name: j.name ?? null,
|
||||
requisition_id: j.requisition_id ?? null,
|
||||
status: j.status ?? null,
|
||||
confidential: j.confidential ?? false,
|
||||
created_at: j.created_at ?? null,
|
||||
opened_at: j.opened_at ?? null,
|
||||
closed_at: j.closed_at ?? null,
|
||||
updated_at: j.updated_at ?? null,
|
||||
is_template: j.is_template ?? null,
|
||||
notes: j.notes ?? null,
|
||||
departments: (j.departments ?? []).map((d: Record<string, unknown>) => ({
|
||||
id: d.id ?? 0,
|
||||
name: (d.name as string) ?? '',
|
||||
parent_id: (d.parent_id as number) ?? null,
|
||||
})),
|
||||
offices: (j.offices ?? []).map((o: Record<string, unknown>) => ({
|
||||
id: o.id ?? 0,
|
||||
name: (o.name as string) ?? '',
|
||||
location: { name: ((o.location as Record<string, unknown>)?.name as string) ?? null },
|
||||
})),
|
||||
hiring_team: {
|
||||
hiring_managers: j.hiring_team?.hiring_managers ?? [],
|
||||
recruiters: j.hiring_team?.recruiters ?? [],
|
||||
coordinators: j.hiring_team?.coordinators ?? [],
|
||||
sourcers: j.hiring_team?.sourcers ?? [],
|
||||
},
|
||||
openings: (j.openings ?? []).map((o: Record<string, unknown>) => ({
|
||||
id: o.id ?? 0,
|
||||
opening_id: (o.opening_id as string) ?? null,
|
||||
status: (o.status as string) ?? 'open',
|
||||
opened_at: (o.opened_at as string) ?? null,
|
||||
closed_at: (o.closed_at as string) ?? null,
|
||||
application_id: (o.application_id as number) ?? null,
|
||||
close_reason: (o.close_reason as { id: number; name: string }) ?? null,
|
||||
})),
|
||||
custom_fields: j.custom_fields ?? {},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'number', description: 'Job ID' },
|
||||
name: { type: 'string', description: 'Job title' },
|
||||
requisition_id: { type: 'string', description: 'External requisition ID', optional: true },
|
||||
status: { type: 'string', description: 'Job status (open, closed, draft)' },
|
||||
confidential: { type: 'boolean', description: 'Whether the job is confidential' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
|
||||
opened_at: { type: 'string', description: 'Date job was opened (ISO 8601)', optional: true },
|
||||
closed_at: { type: 'string', description: 'Date job was closed (ISO 8601)', optional: true },
|
||||
updated_at: { type: 'string', description: 'Last updated timestamp (ISO 8601)' },
|
||||
is_template: { type: 'boolean', description: 'Whether this is a job template', optional: true },
|
||||
notes: { type: 'string', description: 'Hiring plan notes (may contain HTML)', optional: true },
|
||||
departments: {
|
||||
type: 'array',
|
||||
description: 'Associated departments',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Department ID' },
|
||||
name: { type: 'string', description: 'Department name' },
|
||||
parent_id: { type: 'number', description: 'Parent department ID', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
offices: {
|
||||
type: 'array',
|
||||
description: 'Associated offices',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Office ID' },
|
||||
name: { type: 'string', description: 'Office name' },
|
||||
location: {
|
||||
type: 'object',
|
||||
description: 'Office location',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Location name', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
hiring_team: {
|
||||
type: 'object',
|
||||
description: 'Hiring team members',
|
||||
properties: {
|
||||
hiring_managers: { type: 'array', description: 'Hiring managers' },
|
||||
recruiters: { type: 'array', description: 'Recruiters (includes responsible flag)' },
|
||||
coordinators: { type: 'array', description: 'Coordinators (includes responsible flag)' },
|
||||
sourcers: { type: 'array', description: 'Sourcers' },
|
||||
},
|
||||
},
|
||||
openings: {
|
||||
type: 'array',
|
||||
description: 'Job openings/slots',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Opening internal ID' },
|
||||
opening_id: { type: 'string', description: 'Custom opening identifier', optional: true },
|
||||
status: { type: 'string', description: 'Opening status (open, closed)' },
|
||||
opened_at: { type: 'string', description: 'Date opened (ISO 8601)', optional: true },
|
||||
closed_at: { type: 'string', description: 'Date closed (ISO 8601)', optional: true },
|
||||
application_id: { type: 'number', description: 'Hired application ID', optional: true },
|
||||
close_reason: {
|
||||
type: 'object',
|
||||
description: 'Reason for closing',
|
||||
optional: true,
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Close reason ID' },
|
||||
name: { type: 'string', description: 'Close reason name' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
custom_fields: { type: 'object', description: 'Custom field values' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import type { GreenhouseGetUserParams, GreenhouseGetUserResponse } from '@/tools/greenhouse/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const greenhouseGetUserTool: ToolConfig<GreenhouseGetUserParams, GreenhouseGetUserResponse> =
|
||||
{
|
||||
id: 'greenhouse_get_user',
|
||||
name: 'Greenhouse Get User',
|
||||
description: 'Retrieves a specific Greenhouse user by ID',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Greenhouse Harvest API key',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the user to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GreenhouseGetUserParams) =>
|
||||
`https://harvest.greenhouse.io/v1/users/${params.userId.trim()}`,
|
||||
method: 'GET',
|
||||
headers: (params: GreenhouseGetUserParams) => ({
|
||||
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response): Promise<GreenhouseGetUserResponse> => {
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
id: 0,
|
||||
name: null,
|
||||
first_name: null,
|
||||
last_name: null,
|
||||
primary_email_address: null,
|
||||
disabled: false,
|
||||
site_admin: false,
|
||||
emails: [],
|
||||
employee_id: null,
|
||||
linked_candidate_ids: [],
|
||||
created_at: null,
|
||||
updated_at: null,
|
||||
},
|
||||
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
|
||||
}
|
||||
}
|
||||
|
||||
const u = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: u.id ?? 0,
|
||||
name: u.name ?? null,
|
||||
first_name: u.first_name ?? null,
|
||||
last_name: u.last_name ?? null,
|
||||
primary_email_address: u.primary_email_address ?? null,
|
||||
disabled: u.disabled ?? false,
|
||||
site_admin: u.site_admin ?? false,
|
||||
emails: u.emails ?? [],
|
||||
employee_id: u.employee_id ?? null,
|
||||
linked_candidate_ids: u.linked_candidate_ids ?? [],
|
||||
created_at: u.created_at ?? null,
|
||||
updated_at: u.updated_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
name: { type: 'string', description: 'Full name' },
|
||||
first_name: { type: 'string', description: 'First name' },
|
||||
last_name: { type: 'string', description: 'Last name' },
|
||||
primary_email_address: { type: 'string', description: 'Primary email address' },
|
||||
disabled: { type: 'boolean', description: 'Whether the user is disabled' },
|
||||
site_admin: { type: 'boolean', description: 'Whether the user is a site admin' },
|
||||
emails: {
|
||||
type: 'array',
|
||||
description: 'All email addresses',
|
||||
items: { type: 'string', description: 'Email address' },
|
||||
},
|
||||
employee_id: { type: 'string', description: 'Employee ID', optional: true },
|
||||
linked_candidate_ids: {
|
||||
type: 'array',
|
||||
description: 'IDs of candidates linked to this user',
|
||||
items: { type: 'number', description: 'Candidate ID' },
|
||||
},
|
||||
created_at: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
|
||||
updated_at: { type: 'string', description: 'Last updated timestamp (ISO 8601)' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { greenhouseGetApplicationTool } from '@/tools/greenhouse/get_application'
|
||||
import { greenhouseGetCandidateTool } from '@/tools/greenhouse/get_candidate'
|
||||
import { greenhouseGetJobTool } from '@/tools/greenhouse/get_job'
|
||||
import { greenhouseGetUserTool } from '@/tools/greenhouse/get_user'
|
||||
import { greenhouseListApplicationsTool } from '@/tools/greenhouse/list_applications'
|
||||
import { greenhouseListCandidatesTool } from '@/tools/greenhouse/list_candidates'
|
||||
import { greenhouseListDepartmentsTool } from '@/tools/greenhouse/list_departments'
|
||||
import { greenhouseListJobStagesTool } from '@/tools/greenhouse/list_job_stages'
|
||||
import { greenhouseListJobsTool } from '@/tools/greenhouse/list_jobs'
|
||||
import { greenhouseListOfficesTool } from '@/tools/greenhouse/list_offices'
|
||||
import { greenhouseListUsersTool } from '@/tools/greenhouse/list_users'
|
||||
|
||||
export {
|
||||
greenhouseGetApplicationTool,
|
||||
greenhouseGetCandidateTool,
|
||||
greenhouseGetJobTool,
|
||||
greenhouseGetUserTool,
|
||||
greenhouseListApplicationsTool,
|
||||
greenhouseListCandidatesTool,
|
||||
greenhouseListDepartmentsTool,
|
||||
greenhouseListJobStagesTool,
|
||||
greenhouseListJobsTool,
|
||||
greenhouseListOfficesTool,
|
||||
greenhouseListUsersTool,
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
import type {
|
||||
GreenhouseApplicationSummary,
|
||||
GreenhouseListApplicationsParams,
|
||||
GreenhouseListApplicationsResponse,
|
||||
} from '@/tools/greenhouse/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const greenhouseListApplicationsTool: ToolConfig<
|
||||
GreenhouseListApplicationsParams,
|
||||
GreenhouseListApplicationsResponse
|
||||
> = {
|
||||
id: 'greenhouse_list_applications',
|
||||
name: 'Greenhouse List Applications',
|
||||
description: 'Lists applications from Greenhouse with optional filtering by job, status, or date',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Greenhouse Harvest API key',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (1-500, default 100)',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number for pagination',
|
||||
},
|
||||
job_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter applications by job ID',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by status (active, converted, hired, rejected)',
|
||||
},
|
||||
created_after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return only applications created at or after this ISO 8601 timestamp',
|
||||
},
|
||||
created_before: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return only applications created before this ISO 8601 timestamp',
|
||||
},
|
||||
last_activity_after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return only applications with activity at or after this ISO 8601 timestamp',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GreenhouseListApplicationsParams) => {
|
||||
const url = new URL('https://harvest.greenhouse.io/v1/applications')
|
||||
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
|
||||
if (params.page) url.searchParams.append('page', String(params.page))
|
||||
if (params.job_id) url.searchParams.append('job_id', params.job_id)
|
||||
if (params.status) url.searchParams.append('status', params.status)
|
||||
if (params.created_after) url.searchParams.append('created_after', params.created_after)
|
||||
if (params.created_before) url.searchParams.append('created_before', params.created_before)
|
||||
if (params.last_activity_after)
|
||||
url.searchParams.append('last_activity_after', params.last_activity_after)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params: GreenhouseListApplicationsParams) => ({
|
||||
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response): Promise<GreenhouseListApplicationsResponse> => {
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
output: { applications: [], count: 0 },
|
||||
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const applications: GreenhouseApplicationSummary[] = (Array.isArray(data) ? data : []).map(
|
||||
(a: Record<string, unknown>) => ({
|
||||
id: (a.id as number) ?? 0,
|
||||
candidate_id: (a.candidate_id as number) ?? null,
|
||||
prospect: (a.prospect as boolean) ?? false,
|
||||
status: (a.status as string) ?? null,
|
||||
current_stage: (a.current_stage as { id: number; name: string }) ?? null,
|
||||
jobs: (a.jobs as Array<{ id: number; name: string }>) ?? [],
|
||||
applied_at: (a.applied_at as string) ?? null,
|
||||
rejected_at: (a.rejected_at as string) ?? null,
|
||||
last_activity_at: (a.last_activity_at as string) ?? null,
|
||||
})
|
||||
)
|
||||
return {
|
||||
success: true,
|
||||
output: { applications, count: applications.length },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
applications: {
|
||||
type: 'array',
|
||||
description: 'List of applications',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Application ID' },
|
||||
candidate_id: { type: 'number', description: 'Associated candidate ID' },
|
||||
prospect: { type: 'boolean', description: 'Whether this is a prospect application' },
|
||||
status: { type: 'string', description: 'Status (active, converted, hired, rejected)' },
|
||||
current_stage: {
|
||||
type: 'object',
|
||||
description: 'Current interview stage',
|
||||
optional: true,
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Stage ID' },
|
||||
name: { type: 'string', description: 'Stage name' },
|
||||
},
|
||||
},
|
||||
jobs: {
|
||||
type: 'array',
|
||||
description: 'Associated jobs',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Job ID' },
|
||||
name: { type: 'string', description: 'Job name' },
|
||||
},
|
||||
},
|
||||
},
|
||||
applied_at: { type: 'string', description: 'Application date (ISO 8601)' },
|
||||
rejected_at: { type: 'string', description: 'Rejection date (ISO 8601)', optional: true },
|
||||
last_activity_at: { type: 'string', description: 'Last activity date (ISO 8601)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of applications returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
import type {
|
||||
GreenhouseCandidateSummary,
|
||||
GreenhouseListCandidatesParams,
|
||||
GreenhouseListCandidatesResponse,
|
||||
} from '@/tools/greenhouse/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const greenhouseListCandidatesTool: ToolConfig<
|
||||
GreenhouseListCandidatesParams,
|
||||
GreenhouseListCandidatesResponse
|
||||
> = {
|
||||
id: 'greenhouse_list_candidates',
|
||||
name: 'Greenhouse List Candidates',
|
||||
description: 'Lists candidates from Greenhouse with optional filtering by date, job, or email',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Greenhouse Harvest API key',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (1-500, default 100)',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number for pagination',
|
||||
},
|
||||
created_after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return only candidates created at or after this ISO 8601 timestamp',
|
||||
},
|
||||
created_before: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return only candidates created before this ISO 8601 timestamp',
|
||||
},
|
||||
updated_after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return only candidates updated at or after this ISO 8601 timestamp',
|
||||
},
|
||||
updated_before: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return only candidates updated before this ISO 8601 timestamp',
|
||||
},
|
||||
job_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter to candidates who applied to this job ID (excludes prospects)',
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter to candidates with this email address',
|
||||
},
|
||||
candidate_ids: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated candidate IDs to retrieve (max 50)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GreenhouseListCandidatesParams) => {
|
||||
const url = new URL('https://harvest.greenhouse.io/v1/candidates')
|
||||
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
|
||||
if (params.page) url.searchParams.append('page', String(params.page))
|
||||
if (params.created_after) url.searchParams.append('created_after', params.created_after)
|
||||
if (params.created_before) url.searchParams.append('created_before', params.created_before)
|
||||
if (params.updated_after) url.searchParams.append('updated_after', params.updated_after)
|
||||
if (params.updated_before) url.searchParams.append('updated_before', params.updated_before)
|
||||
if (params.job_id) url.searchParams.append('job_id', params.job_id)
|
||||
if (params.email) url.searchParams.append('email_address', params.email)
|
||||
if (params.candidate_ids) url.searchParams.append('candidate_ids', params.candidate_ids)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params: GreenhouseListCandidatesParams) => ({
|
||||
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response): Promise<GreenhouseListCandidatesResponse> => {
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
output: { candidates: [], count: 0 },
|
||||
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const candidates: GreenhouseCandidateSummary[] = (Array.isArray(data) ? data : []).map(
|
||||
(c: Record<string, unknown>) => ({
|
||||
id: (c.id as number) ?? 0,
|
||||
first_name: (c.first_name as string) ?? null,
|
||||
last_name: (c.last_name as string) ?? null,
|
||||
company: (c.company as string) ?? null,
|
||||
title: (c.title as string) ?? null,
|
||||
is_private: (c.is_private as boolean) ?? false,
|
||||
can_email: (c.can_email as boolean) ?? false,
|
||||
email_addresses: (c.email_addresses as Array<{ value: string; type: string }>) ?? [],
|
||||
tags: (c.tags as string[]) ?? [],
|
||||
application_ids: (c.application_ids as number[]) ?? [],
|
||||
created_at: (c.created_at as string) ?? null,
|
||||
updated_at: (c.updated_at as string) ?? null,
|
||||
last_activity: (c.last_activity as string) ?? null,
|
||||
})
|
||||
)
|
||||
return {
|
||||
success: true,
|
||||
output: { candidates, count: candidates.length },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
candidates: {
|
||||
type: 'array',
|
||||
description: 'List of candidates',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Candidate ID' },
|
||||
first_name: { type: 'string', description: 'First name' },
|
||||
last_name: { type: 'string', description: 'Last name' },
|
||||
company: { type: 'string', description: 'Current employer', optional: true },
|
||||
title: { type: 'string', description: 'Current job title', optional: true },
|
||||
is_private: { type: 'boolean', description: 'Whether candidate is private' },
|
||||
can_email: { type: 'boolean', description: 'Whether candidate can be emailed' },
|
||||
email_addresses: {
|
||||
type: 'array',
|
||||
description: 'Email addresses',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
value: { type: 'string', description: 'Email address' },
|
||||
type: { type: 'string', description: 'Email type (personal, work, other)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: {
|
||||
type: 'array',
|
||||
description: 'Candidate tags',
|
||||
items: { type: 'string', description: 'Tag' },
|
||||
},
|
||||
application_ids: {
|
||||
type: 'array',
|
||||
description: 'Associated application IDs',
|
||||
items: { type: 'number', description: 'Application ID' },
|
||||
},
|
||||
created_at: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
|
||||
updated_at: { type: 'string', description: 'Last updated timestamp (ISO 8601)' },
|
||||
last_activity: {
|
||||
type: 'string',
|
||||
description: 'Last activity timestamp (ISO 8601)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of candidates returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import type {
|
||||
GreenhouseDepartment,
|
||||
GreenhouseListDepartmentsParams,
|
||||
GreenhouseListDepartmentsResponse,
|
||||
} from '@/tools/greenhouse/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const greenhouseListDepartmentsTool: ToolConfig<
|
||||
GreenhouseListDepartmentsParams,
|
||||
GreenhouseListDepartmentsResponse
|
||||
> = {
|
||||
id: 'greenhouse_list_departments',
|
||||
name: 'Greenhouse List Departments',
|
||||
description: 'Lists all departments configured in Greenhouse',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Greenhouse Harvest API key',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (1-500, default 100)',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number for pagination',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GreenhouseListDepartmentsParams) => {
|
||||
const url = new URL('https://harvest.greenhouse.io/v1/departments')
|
||||
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
|
||||
if (params.page) url.searchParams.append('page', String(params.page))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params: GreenhouseListDepartmentsParams) => ({
|
||||
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response): Promise<GreenhouseListDepartmentsResponse> => {
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
output: { departments: [], count: 0 },
|
||||
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const departments: GreenhouseDepartment[] = (Array.isArray(data) ? data : []).map(
|
||||
(d: Record<string, unknown>) => ({
|
||||
id: (d.id as number) ?? 0,
|
||||
name: (d.name as string) ?? null,
|
||||
parent_id: (d.parent_id as number) ?? null,
|
||||
child_ids: (d.child_ids as number[]) ?? [],
|
||||
external_id: (d.external_id as string) ?? null,
|
||||
})
|
||||
)
|
||||
return {
|
||||
success: true,
|
||||
output: { departments, count: departments.length },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
departments: {
|
||||
type: 'array',
|
||||
description: 'List of departments',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Department ID' },
|
||||
name: { type: 'string', description: 'Department name' },
|
||||
parent_id: { type: 'number', description: 'Parent department ID', optional: true },
|
||||
child_ids: {
|
||||
type: 'array',
|
||||
description: 'Child department IDs',
|
||||
items: { type: 'number', description: 'Department ID' },
|
||||
},
|
||||
external_id: { type: 'string', description: 'External system ID', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of departments returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
import type {
|
||||
GreenhouseJobStage,
|
||||
GreenhouseJobStageInterview,
|
||||
GreenhouseListJobStagesParams,
|
||||
GreenhouseListJobStagesResponse,
|
||||
GreenhouseUserRef,
|
||||
} from '@/tools/greenhouse/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const greenhouseListJobStagesTool: ToolConfig<
|
||||
GreenhouseListJobStagesParams,
|
||||
GreenhouseListJobStagesResponse
|
||||
> = {
|
||||
id: 'greenhouse_list_job_stages',
|
||||
name: 'Greenhouse List Job Stages',
|
||||
description: 'Lists all interview stages for a specific job in Greenhouse',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Greenhouse Harvest API key',
|
||||
},
|
||||
jobId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The job ID to list stages for',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (1-500, default 100)',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number for pagination',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GreenhouseListJobStagesParams) => {
|
||||
const url = new URL(`https://harvest.greenhouse.io/v1/jobs/${params.jobId.trim()}/stages`)
|
||||
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
|
||||
if (params.page) url.searchParams.append('page', String(params.page))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params: GreenhouseListJobStagesParams) => ({
|
||||
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response): Promise<GreenhouseListJobStagesResponse> => {
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
output: { stages: [], count: 0 },
|
||||
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const stages: GreenhouseJobStage[] = (Array.isArray(data) ? data : []).map(
|
||||
(s: Record<string, unknown>) => ({
|
||||
id: (s.id as number) ?? 0,
|
||||
name: (s.name as string) ?? null,
|
||||
created_at: (s.created_at as string) ?? null,
|
||||
updated_at: (s.updated_at as string) ?? null,
|
||||
job_id: (s.job_id as number) ?? 0,
|
||||
priority: (s.priority as number) ?? 0,
|
||||
active: (s.active as boolean) ?? true,
|
||||
interviews: (Array.isArray(s.interviews) ? s.interviews : []).map(
|
||||
(i: Record<string, unknown>): GreenhouseJobStageInterview => ({
|
||||
id: (i.id as number) ?? 0,
|
||||
name: (i.name as string) ?? null,
|
||||
schedulable: (i.schedulable as boolean) ?? false,
|
||||
estimated_minutes: (i.estimated_minutes as number) ?? null,
|
||||
default_interviewer_users: (Array.isArray(i.default_interviewer_users)
|
||||
? i.default_interviewer_users
|
||||
: []
|
||||
).map(
|
||||
(u: Record<string, unknown>): GreenhouseUserRef => ({
|
||||
id: (u.id as number) ?? 0,
|
||||
first_name: (u.first_name as string) ?? '',
|
||||
last_name: (u.last_name as string) ?? '',
|
||||
name: (u.name as string) ?? '',
|
||||
employee_id: (u.employee_id as string) ?? null,
|
||||
})
|
||||
),
|
||||
interview_kit: i.interview_kit
|
||||
? {
|
||||
id: ((i.interview_kit as Record<string, unknown>).id as number) ?? 0,
|
||||
content: ((i.interview_kit as Record<string, unknown>).content as string) ?? null,
|
||||
questions: (Array.isArray((i.interview_kit as Record<string, unknown>).questions)
|
||||
? (i.interview_kit as Record<string, unknown>).questions
|
||||
: []) as Array<{ id: number; question: string }>,
|
||||
}
|
||||
: null,
|
||||
})
|
||||
),
|
||||
})
|
||||
)
|
||||
return {
|
||||
success: true,
|
||||
output: { stages, count: stages.length },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
stages: {
|
||||
type: 'array',
|
||||
description: 'List of job stages in order',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Stage ID' },
|
||||
name: { type: 'string', description: 'Stage name' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
|
||||
updated_at: { type: 'string', description: 'Last updated timestamp (ISO 8601)' },
|
||||
job_id: { type: 'number', description: 'Associated job ID' },
|
||||
priority: { type: 'number', description: 'Stage order priority' },
|
||||
active: { type: 'boolean', description: 'Whether the stage is active' },
|
||||
interviews: {
|
||||
type: 'array',
|
||||
description: 'Interview steps in this stage',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Interview ID' },
|
||||
name: { type: 'string', description: 'Interview name' },
|
||||
schedulable: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the interview is schedulable',
|
||||
},
|
||||
estimated_minutes: {
|
||||
type: 'number',
|
||||
description: 'Estimated duration in minutes',
|
||||
optional: true,
|
||||
},
|
||||
default_interviewer_users: {
|
||||
type: 'array',
|
||||
description: 'Default interviewers',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
name: { type: 'string', description: 'Full name' },
|
||||
first_name: { type: 'string', description: 'First name' },
|
||||
last_name: { type: 'string', description: 'Last name' },
|
||||
employee_id: { type: 'string', description: 'Employee ID', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
interview_kit: {
|
||||
type: 'object',
|
||||
description: 'Interview kit details',
|
||||
optional: true,
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Kit ID' },
|
||||
content: { type: 'string', description: 'Kit content (HTML)', optional: true },
|
||||
questions: {
|
||||
type: 'array',
|
||||
description: 'Interview kit questions',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Question ID' },
|
||||
question: { type: 'string', description: 'Question text' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of stages returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
import type {
|
||||
GreenhouseJobSummary,
|
||||
GreenhouseListJobsParams,
|
||||
GreenhouseListJobsResponse,
|
||||
} from '@/tools/greenhouse/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const greenhouseListJobsTool: ToolConfig<
|
||||
GreenhouseListJobsParams,
|
||||
GreenhouseListJobsResponse
|
||||
> = {
|
||||
id: 'greenhouse_list_jobs',
|
||||
name: 'Greenhouse List Jobs',
|
||||
description:
|
||||
'Lists jobs from Greenhouse with optional filtering by status, department, or office',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Greenhouse Harvest API key',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (1-500, default 100)',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number for pagination',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by job status (open, closed, draft)',
|
||||
},
|
||||
created_after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return only jobs created at or after this ISO 8601 timestamp',
|
||||
},
|
||||
created_before: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return only jobs created before this ISO 8601 timestamp',
|
||||
},
|
||||
updated_after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return only jobs updated at or after this ISO 8601 timestamp',
|
||||
},
|
||||
updated_before: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return only jobs updated before this ISO 8601 timestamp',
|
||||
},
|
||||
department_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter to jobs in this department ID',
|
||||
},
|
||||
office_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter to jobs in this office ID',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GreenhouseListJobsParams) => {
|
||||
const url = new URL('https://harvest.greenhouse.io/v1/jobs')
|
||||
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
|
||||
if (params.page) url.searchParams.append('page', String(params.page))
|
||||
if (params.status) url.searchParams.append('status', params.status)
|
||||
if (params.created_after) url.searchParams.append('created_after', params.created_after)
|
||||
if (params.created_before) url.searchParams.append('created_before', params.created_before)
|
||||
if (params.updated_after) url.searchParams.append('updated_after', params.updated_after)
|
||||
if (params.updated_before) url.searchParams.append('updated_before', params.updated_before)
|
||||
if (params.department_id) url.searchParams.append('department_id', params.department_id)
|
||||
if (params.office_id) url.searchParams.append('office_id', params.office_id)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params: GreenhouseListJobsParams) => ({
|
||||
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response): Promise<GreenhouseListJobsResponse> => {
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
output: { jobs: [], count: 0 },
|
||||
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const jobs: GreenhouseJobSummary[] = (Array.isArray(data) ? data : []).map(
|
||||
(j: Record<string, unknown>) => ({
|
||||
id: (j.id as number) ?? 0,
|
||||
name: (j.name as string) ?? null,
|
||||
status: (j.status as string) ?? null,
|
||||
confidential: (j.confidential as boolean) ?? false,
|
||||
departments: (j.departments as Array<{ id: number; name: string }>) ?? [],
|
||||
offices: (j.offices as Array<{ id: number; name: string }>) ?? [],
|
||||
opened_at: (j.opened_at as string) ?? null,
|
||||
closed_at: (j.closed_at as string) ?? null,
|
||||
created_at: (j.created_at as string) ?? null,
|
||||
updated_at: (j.updated_at as string) ?? null,
|
||||
})
|
||||
)
|
||||
return {
|
||||
success: true,
|
||||
output: { jobs, count: jobs.length },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
jobs: {
|
||||
type: 'array',
|
||||
description: 'List of jobs',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Job ID' },
|
||||
name: { type: 'string', description: 'Job title' },
|
||||
status: { type: 'string', description: 'Job status (open, closed, draft)' },
|
||||
confidential: { type: 'boolean', description: 'Whether the job is confidential' },
|
||||
departments: {
|
||||
type: 'array',
|
||||
description: 'Associated departments',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Department ID' },
|
||||
name: { type: 'string', description: 'Department name' },
|
||||
},
|
||||
},
|
||||
},
|
||||
offices: {
|
||||
type: 'array',
|
||||
description: 'Associated offices',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Office ID' },
|
||||
name: { type: 'string', description: 'Office name' },
|
||||
},
|
||||
},
|
||||
},
|
||||
opened_at: {
|
||||
type: 'string',
|
||||
description: 'Date job was opened (ISO 8601)',
|
||||
optional: true,
|
||||
},
|
||||
closed_at: {
|
||||
type: 'string',
|
||||
description: 'Date job was closed (ISO 8601)',
|
||||
optional: true,
|
||||
},
|
||||
created_at: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
|
||||
updated_at: { type: 'string', description: 'Last updated timestamp (ISO 8601)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of jobs returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import type {
|
||||
GreenhouseListOfficesParams,
|
||||
GreenhouseListOfficesResponse,
|
||||
GreenhouseOffice,
|
||||
} from '@/tools/greenhouse/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const greenhouseListOfficesTool: ToolConfig<
|
||||
GreenhouseListOfficesParams,
|
||||
GreenhouseListOfficesResponse
|
||||
> = {
|
||||
id: 'greenhouse_list_offices',
|
||||
name: 'Greenhouse List Offices',
|
||||
description: 'Lists all offices configured in Greenhouse',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Greenhouse Harvest API key',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (1-500, default 100)',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number for pagination',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GreenhouseListOfficesParams) => {
|
||||
const url = new URL('https://harvest.greenhouse.io/v1/offices')
|
||||
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
|
||||
if (params.page) url.searchParams.append('page', String(params.page))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params: GreenhouseListOfficesParams) => ({
|
||||
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response): Promise<GreenhouseListOfficesResponse> => {
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
output: { offices: [], count: 0 },
|
||||
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const offices: GreenhouseOffice[] = (Array.isArray(data) ? data : []).map(
|
||||
(o: Record<string, unknown>) => ({
|
||||
id: (o.id as number) ?? 0,
|
||||
name: (o.name as string) ?? null,
|
||||
location: {
|
||||
name: ((o.location as Record<string, unknown>)?.name as string) ?? null,
|
||||
},
|
||||
primary_contact_user_id: (o.primary_contact_user_id as number) ?? null,
|
||||
parent_id: (o.parent_id as number) ?? null,
|
||||
child_ids: (o.child_ids as number[]) ?? [],
|
||||
external_id: (o.external_id as string) ?? null,
|
||||
})
|
||||
)
|
||||
return {
|
||||
success: true,
|
||||
output: { offices, count: offices.length },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
offices: {
|
||||
type: 'array',
|
||||
description: 'List of offices',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Office ID' },
|
||||
name: { type: 'string', description: 'Office name' },
|
||||
location: {
|
||||
type: 'object',
|
||||
description: 'Office location',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Location name', optional: true },
|
||||
},
|
||||
},
|
||||
primary_contact_user_id: {
|
||||
type: 'number',
|
||||
description: 'Primary contact user ID',
|
||||
optional: true,
|
||||
},
|
||||
parent_id: { type: 'number', description: 'Parent office ID', optional: true },
|
||||
child_ids: {
|
||||
type: 'array',
|
||||
description: 'Child office IDs',
|
||||
items: { type: 'number', description: 'Office ID' },
|
||||
},
|
||||
external_id: { type: 'string', description: 'External system ID', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of offices returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
import type {
|
||||
GreenhouseListUsersParams,
|
||||
GreenhouseListUsersResponse,
|
||||
GreenhouseUser,
|
||||
} from '@/tools/greenhouse/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const greenhouseListUsersTool: ToolConfig<
|
||||
GreenhouseListUsersParams,
|
||||
GreenhouseListUsersResponse
|
||||
> = {
|
||||
id: 'greenhouse_list_users',
|
||||
name: 'Greenhouse List Users',
|
||||
description:
|
||||
'Lists Greenhouse users (recruiters, hiring managers, admins) with optional filtering',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Greenhouse Harvest API key',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (1-500, default 100)',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number for pagination',
|
||||
},
|
||||
created_after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return only users created at or after this ISO 8601 timestamp',
|
||||
},
|
||||
created_before: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return only users created before this ISO 8601 timestamp',
|
||||
},
|
||||
updated_after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return only users updated at or after this ISO 8601 timestamp',
|
||||
},
|
||||
updated_before: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return only users updated before this ISO 8601 timestamp',
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by email address',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GreenhouseListUsersParams) => {
|
||||
const url = new URL('https://harvest.greenhouse.io/v1/users')
|
||||
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
|
||||
if (params.page) url.searchParams.append('page', String(params.page))
|
||||
if (params.created_after) url.searchParams.append('created_after', params.created_after)
|
||||
if (params.created_before) url.searchParams.append('created_before', params.created_before)
|
||||
if (params.updated_after) url.searchParams.append('updated_after', params.updated_after)
|
||||
if (params.updated_before) url.searchParams.append('updated_before', params.updated_before)
|
||||
if (params.email) url.searchParams.append('email', params.email)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params: GreenhouseListUsersParams) => ({
|
||||
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response): Promise<GreenhouseListUsersResponse> => {
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
output: { users: [], count: 0 },
|
||||
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const users: GreenhouseUser[] = (Array.isArray(data) ? data : []).map(
|
||||
(u: Record<string, unknown>) => ({
|
||||
id: (u.id as number) ?? 0,
|
||||
name: (u.name as string) ?? null,
|
||||
first_name: (u.first_name as string) ?? null,
|
||||
last_name: (u.last_name as string) ?? null,
|
||||
primary_email_address: (u.primary_email_address as string) ?? null,
|
||||
disabled: (u.disabled as boolean) ?? false,
|
||||
site_admin: (u.site_admin as boolean) ?? false,
|
||||
emails: (u.emails as string[]) ?? [],
|
||||
employee_id: (u.employee_id as string) ?? null,
|
||||
linked_candidate_ids: (u.linked_candidate_ids as number[]) ?? [],
|
||||
created_at: (u.created_at as string) ?? null,
|
||||
updated_at: (u.updated_at as string) ?? null,
|
||||
})
|
||||
)
|
||||
return {
|
||||
success: true,
|
||||
output: { users, count: users.length },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
users: {
|
||||
type: 'array',
|
||||
description: 'List of Greenhouse users',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
name: { type: 'string', description: 'Full name' },
|
||||
first_name: { type: 'string', description: 'First name' },
|
||||
last_name: { type: 'string', description: 'Last name' },
|
||||
primary_email_address: { type: 'string', description: 'Primary email' },
|
||||
disabled: { type: 'boolean', description: 'Whether the user is disabled' },
|
||||
site_admin: { type: 'boolean', description: 'Whether the user is a site admin' },
|
||||
emails: {
|
||||
type: 'array',
|
||||
description: 'All email addresses',
|
||||
items: { type: 'string', description: 'Email address' },
|
||||
},
|
||||
employee_id: { type: 'string', description: 'Employee ID', optional: true },
|
||||
linked_candidate_ids: {
|
||||
type: 'array',
|
||||
description: 'IDs of candidates linked to this user',
|
||||
items: { type: 'number', description: 'Candidate ID' },
|
||||
},
|
||||
created_at: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
|
||||
updated_at: { type: 'string', description: 'Last updated timestamp (ISO 8601)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of users returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Base parameters shared by all Greenhouse tools
|
||||
*/
|
||||
interface GreenhouseBaseParams {
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
// ── List Candidates ──
|
||||
|
||||
export interface GreenhouseListCandidatesParams extends GreenhouseBaseParams {
|
||||
per_page?: number
|
||||
page?: number
|
||||
created_before?: string
|
||||
created_after?: string
|
||||
updated_before?: string
|
||||
updated_after?: string
|
||||
job_id?: string
|
||||
email?: string
|
||||
candidate_ids?: string
|
||||
}
|
||||
|
||||
export interface GreenhouseListCandidatesResponse extends ToolResponse {
|
||||
output: {
|
||||
candidates: GreenhouseCandidateSummary[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
// ── Get Candidate ──
|
||||
|
||||
export interface GreenhouseGetCandidateParams extends GreenhouseBaseParams {
|
||||
candidateId: string
|
||||
}
|
||||
|
||||
export interface GreenhouseGetCandidateResponse extends ToolResponse {
|
||||
output: {
|
||||
id: number
|
||||
first_name: string | null
|
||||
last_name: string | null
|
||||
company: string | null
|
||||
title: string | null
|
||||
is_private: boolean
|
||||
can_email: boolean
|
||||
created_at: string | null
|
||||
updated_at: string | null
|
||||
last_activity: string | null
|
||||
email_addresses: Array<{ value: string; type: string }>
|
||||
phone_numbers: Array<{ value: string; type: string }>
|
||||
addresses: Array<{ value: string; type: string }>
|
||||
website_addresses: Array<{ value: string; type: string }>
|
||||
social_media_addresses: Array<{ value: string }>
|
||||
tags: string[]
|
||||
application_ids: number[]
|
||||
recruiter: GreenhouseUserRef | null
|
||||
coordinator: GreenhouseUserRef | null
|
||||
attachments: GreenhouseAttachment[]
|
||||
educations: GreenhouseEducation[]
|
||||
employments: GreenhouseEmployment[]
|
||||
custom_fields: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
|
||||
// ── List Jobs ──
|
||||
|
||||
export interface GreenhouseListJobsParams extends GreenhouseBaseParams {
|
||||
per_page?: number
|
||||
page?: number
|
||||
status?: string
|
||||
created_before?: string
|
||||
created_after?: string
|
||||
updated_before?: string
|
||||
updated_after?: string
|
||||
department_id?: string
|
||||
office_id?: string
|
||||
}
|
||||
|
||||
export interface GreenhouseListJobsResponse extends ToolResponse {
|
||||
output: {
|
||||
jobs: GreenhouseJobSummary[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
// ── Get Job ──
|
||||
|
||||
export interface GreenhouseGetJobParams extends GreenhouseBaseParams {
|
||||
jobId: string
|
||||
}
|
||||
|
||||
export interface GreenhouseGetJobResponse extends ToolResponse {
|
||||
output: {
|
||||
id: number
|
||||
name: string | null
|
||||
requisition_id: string | null
|
||||
status: string | null
|
||||
confidential: boolean
|
||||
created_at: string | null
|
||||
opened_at: string | null
|
||||
closed_at: string | null
|
||||
updated_at: string | null
|
||||
is_template: boolean | null
|
||||
notes: string | null
|
||||
departments: Array<{ id: number; name: string; parent_id: number | null }>
|
||||
offices: Array<{ id: number; name: string; location: { name: string | null } }>
|
||||
hiring_team: GreenhouseHiringTeam
|
||||
openings: GreenhouseOpening[]
|
||||
custom_fields: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
|
||||
// ── List Applications ──
|
||||
|
||||
export interface GreenhouseListApplicationsParams extends GreenhouseBaseParams {
|
||||
per_page?: number
|
||||
page?: number
|
||||
job_id?: string
|
||||
status?: string
|
||||
created_before?: string
|
||||
created_after?: string
|
||||
last_activity_after?: string
|
||||
}
|
||||
|
||||
export interface GreenhouseListApplicationsResponse extends ToolResponse {
|
||||
output: {
|
||||
applications: GreenhouseApplicationSummary[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
// ── Get Application ──
|
||||
|
||||
export interface GreenhouseGetApplicationParams extends GreenhouseBaseParams {
|
||||
applicationId: string
|
||||
}
|
||||
|
||||
export interface GreenhouseGetApplicationResponse extends ToolResponse {
|
||||
output: {
|
||||
id: number
|
||||
candidate_id: number
|
||||
prospect: boolean
|
||||
status: string | null
|
||||
applied_at: string | null
|
||||
rejected_at: string | null
|
||||
last_activity_at: string | null
|
||||
location: { address: string | null } | null
|
||||
source: { id: number; public_name: string } | null
|
||||
credited_to: GreenhouseUserRef | null
|
||||
recruiter: GreenhouseUserRef | null
|
||||
coordinator: GreenhouseUserRef | null
|
||||
current_stage: { id: number; name: string } | null
|
||||
rejection_reason: { id: number; name: string; type: { id: number; name: string } } | null
|
||||
jobs: Array<{ id: number; name: string }>
|
||||
job_post_id: number | null
|
||||
answers: Array<{ question: string; answer: string }>
|
||||
attachments: GreenhouseAttachment[]
|
||||
custom_fields: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
|
||||
// ── List Users ──
|
||||
|
||||
export interface GreenhouseListUsersParams extends GreenhouseBaseParams {
|
||||
per_page?: number
|
||||
page?: number
|
||||
created_before?: string
|
||||
created_after?: string
|
||||
updated_before?: string
|
||||
updated_after?: string
|
||||
email?: string
|
||||
}
|
||||
|
||||
export interface GreenhouseListUsersResponse extends ToolResponse {
|
||||
output: {
|
||||
users: GreenhouseUser[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
// ── Get User ──
|
||||
|
||||
export interface GreenhouseGetUserParams extends GreenhouseBaseParams {
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface GreenhouseGetUserResponse extends ToolResponse {
|
||||
output: {
|
||||
id: number
|
||||
name: string | null
|
||||
first_name: string | null
|
||||
last_name: string | null
|
||||
primary_email_address: string | null
|
||||
disabled: boolean
|
||||
site_admin: boolean
|
||||
emails: string[]
|
||||
employee_id: string | null
|
||||
linked_candidate_ids: number[]
|
||||
created_at: string | null
|
||||
updated_at: string | null
|
||||
}
|
||||
}
|
||||
|
||||
// ── List Departments ──
|
||||
|
||||
export interface GreenhouseListDepartmentsParams extends GreenhouseBaseParams {
|
||||
per_page?: number
|
||||
page?: number
|
||||
}
|
||||
|
||||
export interface GreenhouseListDepartmentsResponse extends ToolResponse {
|
||||
output: {
|
||||
departments: GreenhouseDepartment[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
// ── List Offices ──
|
||||
|
||||
export interface GreenhouseListOfficesParams extends GreenhouseBaseParams {
|
||||
per_page?: number
|
||||
page?: number
|
||||
}
|
||||
|
||||
export interface GreenhouseListOfficesResponse extends ToolResponse {
|
||||
output: {
|
||||
offices: GreenhouseOffice[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
// ── List Job Stages ──
|
||||
|
||||
export interface GreenhouseListJobStagesParams extends GreenhouseBaseParams {
|
||||
jobId: string
|
||||
per_page?: number
|
||||
page?: number
|
||||
}
|
||||
|
||||
export interface GreenhouseListJobStagesResponse extends ToolResponse {
|
||||
output: {
|
||||
stages: GreenhouseJobStage[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
// ── Shared Types ──
|
||||
|
||||
export interface GreenhouseUserRef {
|
||||
id: number
|
||||
first_name: string
|
||||
last_name: string
|
||||
name: string
|
||||
employee_id: string | null
|
||||
}
|
||||
|
||||
interface GreenhouseAttachment {
|
||||
filename: string
|
||||
url: string
|
||||
type: string
|
||||
created_at: string | null
|
||||
}
|
||||
|
||||
interface GreenhouseEducation {
|
||||
id: number
|
||||
school_name: string | null
|
||||
degree: string | null
|
||||
discipline: string | null
|
||||
start_date: string | null
|
||||
end_date: string | null
|
||||
}
|
||||
|
||||
interface GreenhouseEmployment {
|
||||
id: number
|
||||
company_name: string | null
|
||||
title: string | null
|
||||
start_date: string | null
|
||||
end_date: string | null
|
||||
}
|
||||
|
||||
export interface GreenhouseUser {
|
||||
id: number
|
||||
name: string | null
|
||||
first_name: string | null
|
||||
last_name: string | null
|
||||
primary_email_address: string | null
|
||||
disabled: boolean
|
||||
site_admin: boolean
|
||||
emails: string[]
|
||||
employee_id: string | null
|
||||
linked_candidate_ids: number[]
|
||||
created_at: string | null
|
||||
updated_at: string | null
|
||||
}
|
||||
|
||||
export interface GreenhouseDepartment {
|
||||
id: number
|
||||
name: string | null
|
||||
parent_id: number | null
|
||||
child_ids: number[]
|
||||
external_id: string | null
|
||||
}
|
||||
|
||||
export interface GreenhouseOffice {
|
||||
id: number
|
||||
name: string | null
|
||||
location: { name: string | null }
|
||||
primary_contact_user_id: number | null
|
||||
parent_id: number | null
|
||||
child_ids: number[]
|
||||
external_id: string | null
|
||||
}
|
||||
|
||||
export interface GreenhouseJobStageInterview {
|
||||
id: number
|
||||
name: string | null
|
||||
schedulable: boolean
|
||||
estimated_minutes: number | null
|
||||
default_interviewer_users: GreenhouseUserRef[]
|
||||
interview_kit: {
|
||||
id: number
|
||||
content: string | null
|
||||
questions: Array<{ id: number; question: string }>
|
||||
} | null
|
||||
}
|
||||
|
||||
export interface GreenhouseJobStage {
|
||||
id: number
|
||||
name: string | null
|
||||
created_at: string | null
|
||||
updated_at: string | null
|
||||
job_id: number
|
||||
priority: number
|
||||
active: boolean
|
||||
interviews: GreenhouseJobStageInterview[]
|
||||
}
|
||||
|
||||
export interface GreenhouseCandidateSummary {
|
||||
id: number
|
||||
first_name: string | null
|
||||
last_name: string | null
|
||||
company: string | null
|
||||
title: string | null
|
||||
is_private: boolean
|
||||
can_email: boolean
|
||||
email_addresses: Array<{ value: string; type: string }>
|
||||
tags: string[]
|
||||
application_ids: number[]
|
||||
created_at: string | null
|
||||
updated_at: string | null
|
||||
last_activity: string | null
|
||||
}
|
||||
|
||||
export interface GreenhouseJobSummary {
|
||||
id: number
|
||||
name: string | null
|
||||
status: string | null
|
||||
confidential: boolean
|
||||
departments: Array<{ id: number; name: string }>
|
||||
offices: Array<{ id: number; name: string }>
|
||||
opened_at: string | null
|
||||
closed_at: string | null
|
||||
created_at: string | null
|
||||
updated_at: string | null
|
||||
}
|
||||
|
||||
export interface GreenhouseApplicationSummary {
|
||||
id: number
|
||||
candidate_id: number | null
|
||||
prospect: boolean
|
||||
status: string | null
|
||||
current_stage: { id: number; name: string } | null
|
||||
jobs: Array<{ id: number; name: string }>
|
||||
applied_at: string | null
|
||||
rejected_at: string | null
|
||||
last_activity_at: string | null
|
||||
}
|
||||
|
||||
interface GreenhouseHiringTeam {
|
||||
hiring_managers: GreenhouseUserRef[]
|
||||
recruiters: Array<GreenhouseUserRef & { responsible: boolean }>
|
||||
coordinators: Array<GreenhouseUserRef & { responsible: boolean }>
|
||||
sourcers: GreenhouseUserRef[]
|
||||
}
|
||||
|
||||
interface GreenhouseOpening {
|
||||
id: number
|
||||
opening_id: string | null
|
||||
status: string
|
||||
opened_at: string | null
|
||||
closed_at: string | null
|
||||
application_id: number | null
|
||||
close_reason: { id: number; name: string } | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Union type of all Greenhouse responses
|
||||
*/
|
||||
export type GreenhouseResponse =
|
||||
| GreenhouseListCandidatesResponse
|
||||
| GreenhouseGetCandidateResponse
|
||||
| GreenhouseListJobsResponse
|
||||
| GreenhouseGetJobResponse
|
||||
| GreenhouseListApplicationsResponse
|
||||
| GreenhouseGetApplicationResponse
|
||||
| GreenhouseListUsersResponse
|
||||
| GreenhouseGetUserResponse
|
||||
| GreenhouseListDepartmentsResponse
|
||||
| GreenhouseListOfficesResponse
|
||||
| GreenhouseListJobStagesResponse
|
||||
Reference in New Issue
Block a user