mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
feat(brex): add transfer/budget/spend-limit/vendor write tools (#5558)
* feat(brex): add transfer/budget/spend-limit/vendor write tools
- fix get_company transformResponse reading camelCase accountType instead of Brex's account_type field (always null in prod)
- add brex_create_transfer, brex_create_budget, brex_archive_budget, brex_create_spend_limit, brex_create_vendor, brex_update_vendor
- endpoints verified against Brex's live payments_api.yaml and budgets_api.yaml OpenAPI specs
- guard required money-amount fields against blank/NaN input instead of silently coercing to 0
* fix(brex): correct status enum docs and normalize expense date filters
- get_budget/get_spend_limit output descriptions listed status enum values not in Brex's actual schema
- list_expenses now normalizes purchased_at_start/end through toBrexDateTime for consistency with list_card_transactions/list_cash_transactions
* fix(brex): preserve zero values and normalize booleans in write params
- toRequiredAmount now rejects whitespace-only input (Number(' ') coerces to 0)
- limitBufferPercentage/transactionLimitAmount used truthy checks that dropped explicit 0
- isPproEnabled now normalized to a real boolean instead of forwarding a stringified 'false' from dynamic references
* fix(brex): null-safe PPRO coercion and fail-fast on empty vendor update
- toOptionalBoolean now treats null the same as undefined (was only checking undefined), so a null isPproEnabled from a dynamic reference is omitted instead of coerced to false
- brex_update_vendor throws when no updatable field (companyName/email/phone) is provided, instead of sending an empty PUT body
This commit is contained in:
@@ -4,6 +4,35 @@ import { AuthMode, IntegrationType } from '@/blocks/types'
|
||||
import { normalizeFileInput } from '@/blocks/utils'
|
||||
import type { BrexResponse } from '@/tools/brex/types'
|
||||
|
||||
/** Coerces a required money-amount field to a finite number, throwing on blank/non-numeric input rather than silently sending 0 or NaN to Brex. */
|
||||
function toRequiredAmount(value: unknown, fieldLabel: string): number {
|
||||
if (value == null || (typeof value === 'string' && value.trim() === '')) {
|
||||
throw new Error(`${fieldLabel} must be a valid number`)
|
||||
}
|
||||
const parsed = Number(value)
|
||||
if (!Number.isFinite(parsed)) {
|
||||
throw new Error(`${fieldLabel} must be a valid number`)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
/** Coerces an optional numeric field to a finite number, throwing on non-numeric input instead of silently forwarding NaN. Preserves explicit 0. */
|
||||
function toOptionalFiniteNumber(value: unknown, fieldLabel: string): number | undefined {
|
||||
if (value == null || (typeof value === 'string' && value.trim() === '')) return undefined
|
||||
const parsed = Number(value)
|
||||
if (!Number.isFinite(parsed)) {
|
||||
throw new Error(`${fieldLabel} must be a valid number`)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
/** Normalizes a boolean field that may arrive as a string (e.g. from a dynamic <Block.output> reference) instead of an actual boolean. */
|
||||
function toOptionalBoolean(value: unknown): boolean | undefined {
|
||||
if (value == null) return undefined
|
||||
if (typeof value === 'boolean') return value
|
||||
return String(value).toLowerCase() === 'true'
|
||||
}
|
||||
|
||||
const PAGINATED_OPERATIONS = new Set([
|
||||
'list_expenses',
|
||||
'list_card_transactions',
|
||||
@@ -66,13 +95,19 @@ export const BrexBlock: BlockConfig<BrexResponse> = {
|
||||
// Budgets
|
||||
{ label: 'List Budgets', id: 'list_budgets' },
|
||||
{ label: 'Get Budget', id: 'get_budget' },
|
||||
{ label: 'Create Budget', id: 'create_budget' },
|
||||
{ label: 'Archive Budget', id: 'archive_budget' },
|
||||
{ label: 'List Spend Limits', id: 'list_spend_limits' },
|
||||
{ label: 'Get Spend Limit', id: 'get_spend_limit' },
|
||||
{ label: 'Create Spend Limit', id: 'create_spend_limit' },
|
||||
// Payments
|
||||
{ label: 'List Vendors', id: 'list_vendors' },
|
||||
{ label: 'Get Vendor', id: 'get_vendor' },
|
||||
{ label: 'Create Vendor', id: 'create_vendor' },
|
||||
{ label: 'Update Vendor', id: 'update_vendor' },
|
||||
{ label: 'List Transfers', id: 'list_transfers' },
|
||||
{ label: 'Get Transfer', id: 'get_transfer' },
|
||||
{ label: 'Create Transfer', id: 'create_transfer' },
|
||||
],
|
||||
value: () => 'list_expenses',
|
||||
},
|
||||
@@ -142,11 +177,16 @@ export const BrexBlock: BlockConfig<BrexResponse> = {
|
||||
placeholder: 'ID of the cash account (Get Cash Account defaults to primary)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['list_cash_transactions', 'list_cash_statements', 'get_cash_account'],
|
||||
value: [
|
||||
'list_cash_transactions',
|
||||
'list_cash_statements',
|
||||
'get_cash_account',
|
||||
'create_transfer',
|
||||
],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: ['list_cash_transactions', 'list_cash_statements'],
|
||||
value: ['list_cash_transactions', 'list_cash_statements', 'create_transfer'],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -162,8 +202,8 @@ export const BrexBlock: BlockConfig<BrexResponse> = {
|
||||
title: 'Budget ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'ID of the budget',
|
||||
condition: { field: 'operation', value: 'get_budget' },
|
||||
required: { field: 'operation', value: 'get_budget' },
|
||||
condition: { field: 'operation', value: ['get_budget', 'archive_budget'] },
|
||||
required: { field: 'operation', value: ['get_budget', 'archive_budget'] },
|
||||
},
|
||||
{
|
||||
id: 'spendLimitId',
|
||||
@@ -178,8 +218,8 @@ export const BrexBlock: BlockConfig<BrexResponse> = {
|
||||
title: 'Vendor ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'ID of the vendor',
|
||||
condition: { field: 'operation', value: 'get_vendor' },
|
||||
required: { field: 'operation', value: 'get_vendor' },
|
||||
condition: { field: 'operation', value: ['get_vendor', 'update_vendor'] },
|
||||
required: { field: 'operation', value: ['get_vendor', 'update_vendor'] },
|
||||
},
|
||||
{
|
||||
id: 'transferId',
|
||||
@@ -189,6 +229,305 @@ export const BrexBlock: BlockConfig<BrexResponse> = {
|
||||
condition: { field: 'operation', value: 'get_transfer' },
|
||||
required: { field: 'operation', value: 'get_transfer' },
|
||||
},
|
||||
{
|
||||
id: 'companyName',
|
||||
title: 'Company Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'Name for the vendor (must be unique)',
|
||||
condition: { field: 'operation', value: ['create_vendor', 'update_vendor'] },
|
||||
required: { field: 'operation', value: 'create_vendor' },
|
||||
},
|
||||
{
|
||||
id: 'vendorEmail',
|
||||
title: 'Email',
|
||||
type: 'short-input',
|
||||
placeholder: 'Email address for the vendor',
|
||||
condition: { field: 'operation', value: ['create_vendor', 'update_vendor'] },
|
||||
},
|
||||
{
|
||||
id: 'vendorPhone',
|
||||
title: 'Phone',
|
||||
type: 'short-input',
|
||||
placeholder: 'Phone number for the vendor',
|
||||
condition: { field: 'operation', value: ['create_vendor', 'update_vendor'] },
|
||||
},
|
||||
{
|
||||
id: 'vendorPaymentInstrumentId',
|
||||
title: 'Vendor Payment Instrument ID',
|
||||
type: 'short-input',
|
||||
placeholder:
|
||||
"ID of the vendor's payment instrument to pay (from the vendor's payment accounts)",
|
||||
condition: { field: 'operation', value: 'create_transfer' },
|
||||
required: { field: 'operation', value: 'create_transfer' },
|
||||
},
|
||||
{
|
||||
id: 'amount',
|
||||
title: 'Amount',
|
||||
type: 'short-input',
|
||||
placeholder: 'Amount in the smallest unit of the currency (e.g., cents for USD)',
|
||||
condition: { field: 'operation', value: ['create_transfer', 'create_budget'] },
|
||||
required: { field: 'operation', value: ['create_transfer', 'create_budget'] },
|
||||
},
|
||||
{
|
||||
id: 'currency',
|
||||
title: 'Currency',
|
||||
type: 'short-input',
|
||||
placeholder: 'ISO 4217 currency code (defaults to USD)',
|
||||
mode: 'advanced',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create_transfer', 'create_budget', 'create_spend_limit'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'description',
|
||||
title: 'Description',
|
||||
type: 'long-input',
|
||||
placeholder: 'Description of the transfer, budget, or spend limit',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create_transfer', 'create_budget', 'create_spend_limit'],
|
||||
},
|
||||
required: { field: 'operation', value: ['create_transfer', 'create_budget'] },
|
||||
},
|
||||
{
|
||||
id: 'externalMemo',
|
||||
title: 'External Memo',
|
||||
type: 'short-input',
|
||||
placeholder: 'Memo shown to the recipient (max 90 chars for ACH/Wire, 40 for Cheque)',
|
||||
condition: { field: 'operation', value: 'create_transfer' },
|
||||
required: { field: 'operation', value: 'create_transfer' },
|
||||
},
|
||||
{
|
||||
id: 'approvalType',
|
||||
title: 'Approval Type',
|
||||
type: 'dropdown',
|
||||
options: [{ label: 'Manual approval required', id: 'MANUAL' }],
|
||||
placeholder: 'Default policy applies if left blank',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'create_transfer' },
|
||||
},
|
||||
{
|
||||
id: 'isPproEnabled',
|
||||
title: 'Enable Principal Protection (PPRO)',
|
||||
type: 'switch',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'create_transfer' },
|
||||
},
|
||||
{
|
||||
id: 'resourceName',
|
||||
title: 'Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'Name for the budget or spend limit',
|
||||
condition: { field: 'operation', value: ['create_budget', 'create_spend_limit'] },
|
||||
required: { field: 'operation', value: ['create_budget', 'create_spend_limit'] },
|
||||
},
|
||||
{
|
||||
id: 'parentBudgetId',
|
||||
title: 'Parent Budget ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'ID of the parent budget',
|
||||
condition: { field: 'operation', value: ['create_budget', 'create_spend_limit'] },
|
||||
required: { field: 'operation', value: 'create_budget' },
|
||||
},
|
||||
{
|
||||
id: 'periodRecurrenceType',
|
||||
title: 'Period Recurrence',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Weekly', id: 'WEEKLY' },
|
||||
{ label: 'Monthly', id: 'MONTHLY' },
|
||||
{ label: 'Quarterly', id: 'QUARTERLY' },
|
||||
{ label: 'Yearly', id: 'YEARLY' },
|
||||
{ label: 'One-time', id: 'ONE_TIME' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'create_budget' },
|
||||
required: { field: 'operation', value: 'create_budget' },
|
||||
},
|
||||
{
|
||||
id: 'startDate',
|
||||
title: 'Start Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'Date the budget/spend limit should start counting (YYYY-MM-DD)',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: ['create_budget', 'create_spend_limit'] },
|
||||
},
|
||||
{
|
||||
id: 'endDate',
|
||||
title: 'End Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'Date the budget/spend limit should stop counting (YYYY-MM-DD)',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: ['create_budget', 'create_spend_limit'] },
|
||||
},
|
||||
{
|
||||
id: 'ownerUserIds',
|
||||
title: 'Owner User IDs',
|
||||
type: 'short-input',
|
||||
placeholder: 'Comma-separated user IDs of the budget/spend limit owners',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: ['create_budget', 'create_spend_limit'] },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt:
|
||||
'Generate a comma-separated list of Brex user IDs to set as owners based on the description.\n\nReturn ONLY the comma-separated user IDs - no explanations, no extra text.',
|
||||
placeholder: 'Describe which users should own this budget/spend limit...',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'spendLimitPeriodRecurrenceType',
|
||||
title: 'Period Recurrence',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Per week', id: 'PER_WEEK' },
|
||||
{ label: 'Per month', id: 'PER_MONTH' },
|
||||
{ label: 'Per quarter', id: 'PER_QUARTER' },
|
||||
{ label: 'Per year', id: 'PER_YEAR' },
|
||||
{ label: 'One-time', id: 'ONE_TIME' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'create_spend_limit' },
|
||||
required: { field: 'operation', value: 'create_spend_limit' },
|
||||
},
|
||||
{
|
||||
id: 'spendType',
|
||||
title: 'Spend Type',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Budget-provisioned cards only', id: 'BUDGET_PROVISIONED_CARDS_ONLY' },
|
||||
{
|
||||
label: 'Non-budget-provisioned cards allowed',
|
||||
id: 'NON_BUDGET_PROVISIONED_CARDS_ALLOWED',
|
||||
},
|
||||
],
|
||||
condition: { field: 'operation', value: 'create_spend_limit' },
|
||||
required: { field: 'operation', value: 'create_spend_limit' },
|
||||
},
|
||||
{
|
||||
id: 'expenseVisibility',
|
||||
title: 'Expense Visibility',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Shared with all members', id: 'SHARED' },
|
||||
{ label: 'Private', id: 'PRIVATE' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'create_spend_limit' },
|
||||
required: { field: 'operation', value: 'create_spend_limit' },
|
||||
},
|
||||
{
|
||||
id: 'authorizationVisibility',
|
||||
title: 'Authorization Visibility',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Public to all members', id: 'PUBLIC' },
|
||||
{ label: 'Private to controllers/owners', id: 'PRIVATE' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'create_spend_limit' },
|
||||
required: { field: 'operation', value: 'create_spend_limit' },
|
||||
},
|
||||
{
|
||||
id: 'limitIncreaseSetting',
|
||||
title: 'Limit Increase Requests',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Enabled', id: 'ENABLED' },
|
||||
{ label: 'Disabled', id: 'DISABLED' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'create_spend_limit' },
|
||||
required: { field: 'operation', value: 'create_spend_limit' },
|
||||
},
|
||||
{
|
||||
id: 'autoTransferCardsSetting',
|
||||
title: 'Auto Transfer Cards',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Disabled', id: 'DISABLED' },
|
||||
{ label: 'Enabled', id: 'ENABLED' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'create_spend_limit' },
|
||||
required: { field: 'operation', value: 'create_spend_limit' },
|
||||
},
|
||||
{
|
||||
id: 'autoCreateLimitCardsSetting',
|
||||
title: 'Auto Create Limit Cards',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Disabled', id: 'DISABLED' },
|
||||
{ label: 'All members', id: 'ALL_MEMBERS' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'create_spend_limit' },
|
||||
required: { field: 'operation', value: 'create_spend_limit' },
|
||||
},
|
||||
{
|
||||
id: 'expensePolicyId',
|
||||
title: 'Expense Policy ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'ID of the expense policy for this spend limit',
|
||||
condition: { field: 'operation', value: 'create_spend_limit' },
|
||||
required: { field: 'operation', value: 'create_spend_limit' },
|
||||
},
|
||||
{
|
||||
id: 'baseLimitAmount',
|
||||
title: 'Base Limit Amount',
|
||||
type: 'short-input',
|
||||
placeholder: 'Base limit amount in the smallest unit of the currency (e.g., cents for USD)',
|
||||
condition: { field: 'operation', value: 'create_spend_limit' },
|
||||
required: { field: 'operation', value: 'create_spend_limit' },
|
||||
},
|
||||
{
|
||||
id: 'authorizationType',
|
||||
title: 'Authorization Type',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Hard (declines over available balance)', id: 'HARD' },
|
||||
{ label: 'Soft', id: 'SOFT' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'create_spend_limit' },
|
||||
required: { field: 'operation', value: 'create_spend_limit' },
|
||||
},
|
||||
{
|
||||
id: 'rolloverRefreshRate',
|
||||
title: 'Rollover Refresh Rate',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Off', id: 'OFF' },
|
||||
{ label: 'Never', id: 'NEVER' },
|
||||
{ label: 'Per month', id: 'PER_MONTH' },
|
||||
{ label: 'Per quarter', id: 'PER_QUARTER' },
|
||||
{ label: 'Per year', id: 'PER_YEAR' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'create_spend_limit' },
|
||||
required: { field: 'operation', value: 'create_spend_limit' },
|
||||
},
|
||||
{
|
||||
id: 'limitBufferPercentage',
|
||||
title: 'Limit Buffer Percentage',
|
||||
type: 'short-input',
|
||||
placeholder: 'Flexible buffer on the limit as a 0-100 percentage',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'create_spend_limit' },
|
||||
},
|
||||
{
|
||||
id: 'transactionLimitAmount',
|
||||
title: 'Transaction Limit Amount',
|
||||
type: 'short-input',
|
||||
placeholder: 'Per-transaction limit in the smallest unit of the currency',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'create_spend_limit' },
|
||||
},
|
||||
{
|
||||
id: 'spendLimitMemberUserIds',
|
||||
title: 'Member User IDs',
|
||||
type: 'short-input',
|
||||
placeholder: 'Comma-separated user IDs of the spend limit members',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'create_spend_limit' },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt:
|
||||
'Generate a comma-separated list of Brex user IDs to set as spend limit members based on the description.\n\nReturn ONLY the comma-separated user IDs - no explanations, no extra text.',
|
||||
placeholder: 'Describe which users should be members of this spend limit...',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'email',
|
||||
title: 'Email',
|
||||
@@ -388,12 +727,18 @@ export const BrexBlock: BlockConfig<BrexResponse> = {
|
||||
'brex_get_company',
|
||||
'brex_list_budgets',
|
||||
'brex_get_budget',
|
||||
'brex_create_budget',
|
||||
'brex_archive_budget',
|
||||
'brex_list_spend_limits',
|
||||
'brex_get_spend_limit',
|
||||
'brex_create_spend_limit',
|
||||
'brex_list_vendors',
|
||||
'brex_get_vendor',
|
||||
'brex_create_vendor',
|
||||
'brex_update_vendor',
|
||||
'brex_list_transfers',
|
||||
'brex_get_transfer',
|
||||
'brex_create_transfer',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => `brex_${params.operation}`,
|
||||
@@ -459,15 +804,88 @@ export const BrexBlock: BlockConfig<BrexResponse> = {
|
||||
case 'get_budget':
|
||||
result.budgetId = params.budgetId
|
||||
break
|
||||
case 'create_budget': {
|
||||
result.name = params.resourceName
|
||||
result.description = params.description
|
||||
result.parentBudgetId = params.parentBudgetId
|
||||
result.periodRecurrenceType = params.periodRecurrenceType
|
||||
result.amount = toRequiredAmount(params.amount, 'Amount')
|
||||
if (params.currency) result.currency = params.currency
|
||||
if (params.ownerUserIds) result.ownerUserIds = params.ownerUserIds
|
||||
if (params.startDate) result.startDate = params.startDate
|
||||
if (params.endDate) result.endDate = params.endDate
|
||||
break
|
||||
}
|
||||
case 'archive_budget':
|
||||
result.budgetId = params.budgetId
|
||||
break
|
||||
case 'get_spend_limit':
|
||||
result.spendLimitId = params.spendLimitId
|
||||
break
|
||||
case 'create_spend_limit': {
|
||||
result.name = params.resourceName
|
||||
result.periodRecurrenceType = params.spendLimitPeriodRecurrenceType
|
||||
result.spendType = params.spendType
|
||||
result.expenseVisibility = params.expenseVisibility
|
||||
result.authorizationVisibility = params.authorizationVisibility
|
||||
result.limitIncreaseSetting = params.limitIncreaseSetting
|
||||
result.autoTransferCardsSetting = params.autoTransferCardsSetting
|
||||
result.autoCreateLimitCardsSetting = params.autoCreateLimitCardsSetting
|
||||
result.expensePolicyId = params.expensePolicyId
|
||||
result.baseLimitAmount = toRequiredAmount(params.baseLimitAmount, 'Base limit amount')
|
||||
if (params.currency) result.currency = params.currency
|
||||
result.authorizationType = params.authorizationType
|
||||
result.rolloverRefreshRate = params.rolloverRefreshRate
|
||||
const limitBufferPercentage = toOptionalFiniteNumber(
|
||||
params.limitBufferPercentage,
|
||||
'Limit buffer percentage'
|
||||
)
|
||||
if (limitBufferPercentage !== undefined)
|
||||
result.limitBufferPercentage = limitBufferPercentage
|
||||
if (params.description) result.description = params.description
|
||||
if (params.parentBudgetId) result.parentBudgetId = params.parentBudgetId
|
||||
if (params.startDate) result.startDate = params.startDate
|
||||
if (params.endDate) result.endDate = params.endDate
|
||||
const transactionLimitAmount = toOptionalFiniteNumber(
|
||||
params.transactionLimitAmount,
|
||||
'Transaction limit amount'
|
||||
)
|
||||
if (transactionLimitAmount !== undefined)
|
||||
result.transactionLimitAmount = transactionLimitAmount
|
||||
if (params.ownerUserIds) result.ownerUserIds = params.ownerUserIds
|
||||
if (params.spendLimitMemberUserIds)
|
||||
result.memberUserIds = params.spendLimitMemberUserIds
|
||||
break
|
||||
}
|
||||
case 'get_vendor':
|
||||
result.vendorId = params.vendorId
|
||||
break
|
||||
case 'create_vendor':
|
||||
result.companyName = params.companyName
|
||||
if (params.vendorEmail) result.email = params.vendorEmail
|
||||
if (params.vendorPhone) result.phone = params.vendorPhone
|
||||
break
|
||||
case 'update_vendor':
|
||||
result.vendorId = params.vendorId
|
||||
if (params.companyName) result.companyName = params.companyName
|
||||
if (params.vendorEmail) result.email = params.vendorEmail
|
||||
if (params.vendorPhone) result.phone = params.vendorPhone
|
||||
break
|
||||
case 'get_transfer':
|
||||
result.transferId = params.transferId
|
||||
break
|
||||
case 'create_transfer': {
|
||||
result.cashAccountId = params.accountId
|
||||
result.vendorPaymentInstrumentId = params.vendorPaymentInstrumentId
|
||||
result.amount = toRequiredAmount(params.amount, 'Amount')
|
||||
if (params.currency) result.currency = params.currency
|
||||
result.description = params.description
|
||||
result.externalMemo = params.externalMemo
|
||||
if (params.approvalType) result.approvalType = params.approvalType
|
||||
const pproEnabled = toOptionalBoolean(params.isPproEnabled)
|
||||
if (pproEnabled !== undefined) result.isPproEnabled = pproEnabled
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -508,6 +926,76 @@ export const BrexBlock: BlockConfig<BrexResponse> = {
|
||||
},
|
||||
cursor: { type: 'string', description: 'Pagination cursor' },
|
||||
limit: { type: 'string', description: 'Number of results to return' },
|
||||
companyName: { type: 'string', description: 'Vendor company name' },
|
||||
vendorEmail: { type: 'string', description: 'Vendor email address' },
|
||||
vendorPhone: { type: 'string', description: 'Vendor phone number' },
|
||||
vendorPaymentInstrumentId: {
|
||||
type: 'string',
|
||||
description: "Vendor's payment instrument ID for the transfer counterparty",
|
||||
},
|
||||
amount: { type: 'string', description: 'Amount in the smallest unit of the currency' },
|
||||
currency: { type: 'string', description: 'ISO 4217 currency code' },
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'Description of the transfer, budget, or spend limit',
|
||||
},
|
||||
externalMemo: { type: 'string', description: 'External memo shown to the transfer recipient' },
|
||||
approvalType: { type: 'string', description: 'Transfer approval type (MANUAL)' },
|
||||
isPproEnabled: {
|
||||
type: 'boolean',
|
||||
description: 'Whether to enable Principal Protection (PPRO) on the transfer',
|
||||
},
|
||||
resourceName: { type: 'string', description: 'Name for the budget or spend limit' },
|
||||
parentBudgetId: { type: 'string', description: 'Parent budget ID' },
|
||||
periodRecurrenceType: {
|
||||
type: 'string',
|
||||
description: 'Budget period recurrence (WEEKLY, MONTHLY, QUARTERLY, YEARLY, ONE_TIME)',
|
||||
},
|
||||
startDate: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
|
||||
endDate: { type: 'string', description: 'End date (YYYY-MM-DD)' },
|
||||
ownerUserIds: { type: 'string', description: 'Comma-separated owner user IDs' },
|
||||
spendLimitPeriodRecurrenceType: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Spend limit period recurrence (PER_WEEK, PER_MONTH, PER_QUARTER, PER_YEAR, ONE_TIME)',
|
||||
},
|
||||
spendType: { type: 'string', description: 'Spend limit spend type' },
|
||||
expenseVisibility: {
|
||||
type: 'string',
|
||||
description: 'Spend limit expense visibility (SHARED, PRIVATE)',
|
||||
},
|
||||
authorizationVisibility: {
|
||||
type: 'string',
|
||||
description: 'Spend limit authorization visibility (PUBLIC, PRIVATE)',
|
||||
},
|
||||
limitIncreaseSetting: {
|
||||
type: 'string',
|
||||
description: 'Whether members can request limit increases',
|
||||
},
|
||||
autoTransferCardsSetting: {
|
||||
type: 'string',
|
||||
description: 'Auto transfer setting for virtual cards',
|
||||
},
|
||||
autoCreateLimitCardsSetting: {
|
||||
type: 'string',
|
||||
description: 'Auto limit card creation setting',
|
||||
},
|
||||
expensePolicyId: { type: 'string', description: 'Expense policy ID for the spend limit' },
|
||||
baseLimitAmount: {
|
||||
type: 'string',
|
||||
description: 'Base spend limit amount before increases/rollovers',
|
||||
},
|
||||
authorizationType: {
|
||||
type: 'string',
|
||||
description: 'Spend limit authorization type (HARD, SOFT)',
|
||||
},
|
||||
rolloverRefreshRate: { type: 'string', description: 'Spend limit rollover refresh rate' },
|
||||
limitBufferPercentage: { type: 'string', description: 'Flexible buffer percentage (0-100)' },
|
||||
transactionLimitAmount: { type: 'string', description: 'Per-transaction limit amount' },
|
||||
spendLimitMemberUserIds: {
|
||||
type: 'string',
|
||||
description: 'Comma-separated member user IDs for a new spend limit',
|
||||
},
|
||||
},
|
||||
outputs: {
|
||||
items: { type: 'json', description: 'Items returned by list operations' },
|
||||
@@ -543,7 +1031,7 @@ export const BrexBlock: BlockConfig<BrexResponse> = {
|
||||
expenseId: { type: 'string', description: 'ID of the expense the receipt was attached to' },
|
||||
firstName: { type: 'string', description: 'First name of the user' },
|
||||
lastName: { type: 'string', description: 'Last name of the user' },
|
||||
email: { type: 'string', description: 'Email address of the user' },
|
||||
email: { type: 'string', description: 'Email address of the user or vendor' },
|
||||
managerId: { type: 'string', description: 'Manager ID of the user' },
|
||||
titleId: { type: 'string', description: 'Title ID of the user' },
|
||||
legalName: { type: 'string', description: 'Legal name of the company' },
|
||||
@@ -660,6 +1148,15 @@ export const BrexBlockMeta = {
|
||||
tags: ['automation'],
|
||||
alsoIntegrations: ['gmail'],
|
||||
},
|
||||
{
|
||||
icon: BrexIcon,
|
||||
title: 'Brex approved-invoice payment automation',
|
||||
prompt:
|
||||
'Build a workflow that takes an approved invoice (vendor name, amount, and memo), creates the vendor in Brex if it does not already exist, and creates a transfer from the company cash account to pay it.',
|
||||
modules: ['workflows'],
|
||||
category: 'operations',
|
||||
tags: ['automation'],
|
||||
},
|
||||
{
|
||||
icon: BrexIcon,
|
||||
title: 'Brex team directory assistant',
|
||||
@@ -717,5 +1214,11 @@ export const BrexBlockMeta = {
|
||||
content:
|
||||
'# Statement Reconciliation\n\nTie a card statement back to its underlying transactions.\n\n## Steps\n1. List card statements and pick the period to reconcile.\n2. List card transactions posted within that period using the posted-at filter.\n3. Compare transaction totals to the statement start and end balances and flag gaps.\n\n## Output\nReturn the statement period, its balances, the transaction total for the period, and any discrepancy that needs review.',
|
||||
},
|
||||
{
|
||||
name: 'pay-vendor',
|
||||
description: 'Pay a vendor from a Brex cash account, creating the vendor first if needed.',
|
||||
content:
|
||||
"# Pay a Vendor\n\nSend a payment to a vendor through Brex.\n\n## Steps\n1. List vendors and check if the target vendor already exists by name.\n2. If not, use Create Vendor with the company name (and email/phone if known).\n3. Use the vendor's payment_accounts entry to get the payment_instrument_id, then use Create Transfer with that ID, the cash account to pay from, the amount (in cents), a description, and an external memo.\n4. Confirm the transfer was created and note its status.\n\n## Output\nReturn the vendor used, the transfer ID and status, and the amount sent. Flag anything that requires manual approval (PENDING_APPROVAL status).",
|
||||
},
|
||||
],
|
||||
} as const satisfies BlockMeta
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { BrexArchiveBudgetParams, BrexArchiveBudgetResponse } from '@/tools/brex/types'
|
||||
import { BREX_API_BASE, buildBrexHeaders, parseBrexJson } from '@/tools/brex/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const brexArchiveBudgetTool: ToolConfig<BrexArchiveBudgetParams, BrexArchiveBudgetResponse> =
|
||||
{
|
||||
id: 'brex_archive_budget',
|
||||
name: 'Brex Archive Budget',
|
||||
description:
|
||||
'Archive a Brex budget, making any spend limits beneath it unusable for future expenses and removing it from the UI',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Brex user token (generated from Developer Settings in the Brex dashboard)',
|
||||
},
|
||||
budgetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the budget to archive',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`${BREX_API_BASE}/v2/budgets/${encodeURIComponent(params.budgetId.trim())}/archive`,
|
||||
method: 'POST',
|
||||
headers: (params) => buildBrexHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
if (!response.ok) {
|
||||
// parseBrexJson throws a descriptive error for non-2xx responses; it never
|
||||
// returns in this branch since the body cannot be a successful JSON payload.
|
||||
await parseBrexJson(response)
|
||||
}
|
||||
|
||||
// Brex's archive endpoint does not document a response body schema; fall back
|
||||
// to the request's budget ID and an ARCHIVED status when the body is empty.
|
||||
let data: Record<string, unknown> = {}
|
||||
const text = await response.text()
|
||||
if (text) {
|
||||
try {
|
||||
data = JSON.parse(text)
|
||||
} catch {
|
||||
data = {}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
budgetId: (data.budget_id as string) ?? params?.budgetId ?? '',
|
||||
spendBudgetStatus: (data.spend_budget_status as string) ?? 'ARCHIVED',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
budgetId: { type: 'string', description: 'ID of the archived budget' },
|
||||
spendBudgetStatus: {
|
||||
type: 'string',
|
||||
description: 'Status of the budget after archiving',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import { generateId } from '@sim/utils/id'
|
||||
import type { BrexCreateBudgetParams, BrexCreateBudgetResponse } from '@/tools/brex/types'
|
||||
import { BREX_MONEY_PROPERTIES } from '@/tools/brex/types'
|
||||
import { BREX_API_BASE, buildBrexHeaders, parseBrexJson, splitBrexIdList } from '@/tools/brex/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const brexCreateBudgetTool: ToolConfig<BrexCreateBudgetParams, BrexCreateBudgetResponse> = {
|
||||
id: 'brex_create_budget',
|
||||
name: 'Brex Create Budget',
|
||||
description: 'Create a new budget in the Brex account',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Brex user token (generated from Developer Settings in the Brex dashboard)',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name for the budget',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Description of what the budget is used for',
|
||||
},
|
||||
parentBudgetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the parent budget',
|
||||
},
|
||||
periodRecurrenceType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Period type of the budget (WEEKLY, MONTHLY, QUARTERLY, YEARLY, ONE_TIME)',
|
||||
},
|
||||
amount: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Budget amount, in the smallest unit of the currency (e.g., cents for USD)',
|
||||
},
|
||||
currency: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ISO 4217 currency code (defaults to USD)',
|
||||
},
|
||||
ownerUserIds: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated user IDs of the budget owners',
|
||||
},
|
||||
startDate: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Date the budget should start counting (YYYY-MM-DD)',
|
||||
},
|
||||
endDate: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Date the budget should stop counting (YYYY-MM-DD)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => `${BREX_API_BASE}/v2/budgets`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
...buildBrexHeaders(params.apiKey),
|
||||
'Idempotency-Key': generateId(),
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {
|
||||
name: params.name,
|
||||
description: params.description,
|
||||
parent_budget_id: params.parentBudgetId,
|
||||
period_recurrence_type: params.periodRecurrenceType,
|
||||
amount: {
|
||||
amount: params.amount,
|
||||
currency: params.currency || 'USD',
|
||||
},
|
||||
}
|
||||
const ownerUserIds = splitBrexIdList(params.ownerUserIds)
|
||||
if (ownerUserIds) body.owner_user_ids = ownerUserIds
|
||||
if (params.startDate) body.start_date = params.startDate
|
||||
if (params.endDate) body.end_date = params.endDate
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await parseBrexJson(response)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
budgetId: data.budget_id ?? '',
|
||||
accountId: data.account_id ?? '',
|
||||
name: data.name ?? '',
|
||||
description: data.description ?? null,
|
||||
parentBudgetId: data.parent_budget_id ?? null,
|
||||
ownerUserIds: data.owner_user_ids ?? [],
|
||||
periodRecurrenceType: data.period_recurrence_type ?? '',
|
||||
startDate: data.start_date ?? null,
|
||||
endDate: data.end_date ?? null,
|
||||
amount: data.amount ?? null,
|
||||
spendBudgetStatus: data.spend_budget_status ?? '',
|
||||
limitType: data.limit_type ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
budgetId: { type: 'string', description: 'Unique budget ID' },
|
||||
accountId: { type: 'string', description: 'Account ID the budget belongs to' },
|
||||
name: { type: 'string', description: 'Budget name' },
|
||||
description: { type: 'string', description: 'Budget description', optional: true },
|
||||
parentBudgetId: { type: 'string', description: 'Parent budget ID', optional: true },
|
||||
ownerUserIds: { type: 'array', description: 'User IDs of the budget owners' },
|
||||
periodRecurrenceType: {
|
||||
type: 'string',
|
||||
description: 'Budget period recurrence (WEEKLY, MONTHLY, QUARTERLY, YEARLY, ONE_TIME)',
|
||||
},
|
||||
startDate: { type: 'string', description: 'Budget start date', optional: true },
|
||||
endDate: { type: 'string', description: 'Budget end date', optional: true },
|
||||
amount: {
|
||||
type: 'json',
|
||||
description: 'Budget amount',
|
||||
optional: true,
|
||||
properties: BREX_MONEY_PROPERTIES,
|
||||
},
|
||||
spendBudgetStatus: { type: 'string', description: 'Status of the created budget' },
|
||||
limitType: { type: 'string', description: 'Budget limit type', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
import { generateId } from '@sim/utils/id'
|
||||
import type { BrexCreateSpendLimitParams, BrexCreateSpendLimitResponse } from '@/tools/brex/types'
|
||||
import { BREX_SPEND_LIMIT_PERIOD_BALANCE_PROPERTIES } from '@/tools/brex/types'
|
||||
import { BREX_API_BASE, buildBrexHeaders, parseBrexJson, splitBrexIdList } from '@/tools/brex/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const brexCreateSpendLimitTool: ToolConfig<
|
||||
BrexCreateSpendLimitParams,
|
||||
BrexCreateSpendLimitResponse
|
||||
> = {
|
||||
id: 'brex_create_spend_limit',
|
||||
name: 'Brex Create Spend Limit',
|
||||
description: 'Create a new spend limit (hard-authorization card program) in the Brex account',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Brex user token (generated from Developer Settings in the Brex dashboard)',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name for the spend limit',
|
||||
},
|
||||
periodRecurrenceType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Period type of the spend limit (PER_WEEK, PER_MONTH, PER_QUARTER, PER_YEAR, ONE_TIME)',
|
||||
},
|
||||
spendType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Whether the spend limit can only be spent from cards it provisions (BUDGET_PROVISIONED_CARDS_ONLY, NON_BUDGET_PROVISIONED_CARDS_ALLOWED)',
|
||||
},
|
||||
expenseVisibility: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Whether expenses on this spend limit are viewable by all members (SHARED, PRIVATE)',
|
||||
},
|
||||
authorizationVisibility: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Whether the limit amount is visible to all members, or just controllers/bookkeepers/owners (PUBLIC, PRIVATE)',
|
||||
},
|
||||
limitIncreaseSetting: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether members can request limit increases (ENABLED, DISABLED)',
|
||||
},
|
||||
autoTransferCardsSetting: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'How auto transfer works for virtual cards on this spend limit (DISABLED, ENABLED)',
|
||||
},
|
||||
autoCreateLimitCardsSetting: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'How auto limit card creation works for members (DISABLED, ALL_MEMBERS)',
|
||||
},
|
||||
expensePolicyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the expense policy corresponding to this spend limit',
|
||||
},
|
||||
baseLimitAmount: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Base spend limit amount, without increases/rollovers, in the smallest unit of the currency (e.g., cents for USD)',
|
||||
},
|
||||
currency: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ISO 4217 currency code for the base limit (defaults to USD)',
|
||||
},
|
||||
authorizationType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether authorizations decline based on available balance (HARD, SOFT)',
|
||||
},
|
||||
rolloverRefreshRate: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Recurrence at which rolled-over unused funds stop rolling over (OFF, NEVER, PER_MONTH, PER_QUARTER, PER_YEAR)',
|
||||
},
|
||||
limitBufferPercentage: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Flexible buffer on the limit as a 0-100 percentage',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Description of what the spend limit is used for',
|
||||
},
|
||||
parentBudgetId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the parent budget',
|
||||
},
|
||||
startDate: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Date the spend limit should start counting (YYYY-MM-DD)',
|
||||
},
|
||||
endDate: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Date the spend limit should expire (YYYY-MM-DD)',
|
||||
},
|
||||
transactionLimitAmount: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Per-transaction limit this spend limit enforces, in the smallest unit of the currency',
|
||||
},
|
||||
ownerUserIds: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated user IDs of the spend limit owners',
|
||||
},
|
||||
memberUserIds: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated user IDs of the spend limit members',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => `${BREX_API_BASE}/v2/spend_limits`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
...buildBrexHeaders(params.apiKey),
|
||||
'Idempotency-Key': generateId(),
|
||||
}),
|
||||
body: (params) => {
|
||||
const currency = params.currency || 'USD'
|
||||
const body: Record<string, unknown> = {
|
||||
name: params.name,
|
||||
period_recurrence_type: params.periodRecurrenceType,
|
||||
spend_type: params.spendType,
|
||||
expense_visibility: params.expenseVisibility,
|
||||
authorization_visibility: params.authorizationVisibility,
|
||||
limit_increase_setting: params.limitIncreaseSetting,
|
||||
auto_transfer_cards_setting: params.autoTransferCardsSetting,
|
||||
auto_create_limit_cards_setting: params.autoCreateLimitCardsSetting,
|
||||
expense_policy_id: params.expensePolicyId,
|
||||
authorization_settings: {
|
||||
base_limit: {
|
||||
amount: params.baseLimitAmount,
|
||||
currency,
|
||||
},
|
||||
authorization_type: params.authorizationType,
|
||||
rollover_refresh_rate: params.rolloverRefreshRate,
|
||||
...(params.limitBufferPercentage !== undefined
|
||||
? { limit_buffer_percentage: params.limitBufferPercentage }
|
||||
: {}),
|
||||
},
|
||||
}
|
||||
if (params.description) body.description = params.description
|
||||
if (params.parentBudgetId) body.parent_budget_id = params.parentBudgetId
|
||||
if (params.startDate) body.start_date = params.startDate
|
||||
if (params.endDate) body.end_date = params.endDate
|
||||
if (params.transactionLimitAmount !== undefined) {
|
||||
body.transaction_limit = { amount: params.transactionLimitAmount, currency }
|
||||
}
|
||||
const ownerUserIds = splitBrexIdList(params.ownerUserIds)
|
||||
if (ownerUserIds) body.owner_user_ids = ownerUserIds
|
||||
const memberUserIds = splitBrexIdList(params.memberUserIds)
|
||||
if (memberUserIds) body.member_user_ids = memberUserIds
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await parseBrexJson(response)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id ?? '',
|
||||
accountId: data.account_id ?? '',
|
||||
name: data.name ?? '',
|
||||
description: data.description ?? null,
|
||||
parentBudgetId: data.parent_budget_id ?? null,
|
||||
status: data.status ?? '',
|
||||
periodRecurrenceType: data.period_recurrence_type ?? '',
|
||||
spendType: data.spend_type ?? '',
|
||||
startDate: data.start_date ?? null,
|
||||
endDate: data.end_date ?? null,
|
||||
ownerUserIds: data.owner_user_ids ?? [],
|
||||
memberUserIds: data.member_user_ids ?? [],
|
||||
currentPeriodBalance: data.current_period_balance ?? null,
|
||||
authorizationSettings: data.authorization_settings ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Unique spend limit ID' },
|
||||
accountId: { type: 'string', description: 'Account ID the spend limit belongs to' },
|
||||
name: { type: 'string', description: 'Spend limit name' },
|
||||
description: { type: 'string', description: 'Spend limit description', optional: true },
|
||||
parentBudgetId: { type: 'string', description: 'Parent budget ID', optional: true },
|
||||
status: { type: 'string', description: 'Spend limit status' },
|
||||
periodRecurrenceType: {
|
||||
type: 'string',
|
||||
description: 'Period recurrence (PER_WEEK, PER_MONTH, PER_QUARTER, PER_YEAR, ONE_TIME)',
|
||||
},
|
||||
spendType: { type: 'string', description: 'Spend type of the limit' },
|
||||
startDate: { type: 'string', description: 'Spend limit start date', optional: true },
|
||||
endDate: { type: 'string', description: 'Spend limit end date', optional: true },
|
||||
ownerUserIds: { type: 'array', description: 'User IDs of the spend limit owners' },
|
||||
memberUserIds: { type: 'array', description: 'User IDs of the spend limit members' },
|
||||
currentPeriodBalance: {
|
||||
type: 'json',
|
||||
description: 'Spend and rollover amounts for the current period',
|
||||
optional: true,
|
||||
properties: BREX_SPEND_LIMIT_PERIOD_BALANCE_PROPERTIES,
|
||||
},
|
||||
authorizationSettings: {
|
||||
type: 'json',
|
||||
description: 'Authorization settings (base limit, authorization type, rollover refresh)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
import { generateId } from '@sim/utils/id'
|
||||
import type { BrexCreateTransferParams, BrexCreateTransferResponse } from '@/tools/brex/types'
|
||||
import { BREX_MONEY_PROPERTIES } from '@/tools/brex/types'
|
||||
import { BREX_API_BASE, buildBrexHeaders, parseBrexJson } from '@/tools/brex/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const brexCreateTransferTool: ToolConfig<
|
||||
BrexCreateTransferParams,
|
||||
BrexCreateTransferResponse
|
||||
> = {
|
||||
id: 'brex_create_transfer',
|
||||
name: 'Brex Create Transfer',
|
||||
description: 'Create a money transfer from a Brex cash account to a vendor',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Brex user token (generated from Developer Settings in the Brex dashboard)',
|
||||
},
|
||||
cashAccountId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'ID of the Brex cash account to send the transfer from (found via the /accounts endpoint)',
|
||||
},
|
||||
vendorPaymentInstrumentId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
"ID of the vendor's payment instrument to send the transfer to (from the vendor's payment_accounts)",
|
||||
},
|
||||
amount: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Amount to transfer, in the smallest unit of the currency (e.g., cents for USD)',
|
||||
},
|
||||
currency: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ISO 4217 currency code (defaults to USD)',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Description of the transfer for internal use (not exposed externally)',
|
||||
},
|
||||
externalMemo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'External memo shown to the recipient (max 90 characters for ACH/Wire, 40 for Cheque)',
|
||||
},
|
||||
approvalType: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Set to MANUAL to require cash admin approval before the transfer is sent',
|
||||
},
|
||||
isPproEnabled: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Enable Principal Protection (PPRO) to have Brex cover intermediary/receiving bank fees (international wires only)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => `${BREX_API_BASE}/v1/transfers`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
...buildBrexHeaders(params.apiKey),
|
||||
// Brex requires a fresh Idempotency-Key per transfer creation to prevent duplicate money movement.
|
||||
'Idempotency-Key': generateId(),
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {
|
||||
counterparty: {
|
||||
type: 'VENDOR',
|
||||
payment_instrument_id: params.vendorPaymentInstrumentId,
|
||||
},
|
||||
amount: {
|
||||
amount: params.amount,
|
||||
currency: params.currency || 'USD',
|
||||
},
|
||||
description: params.description,
|
||||
external_memo: params.externalMemo,
|
||||
originating_account: {
|
||||
type: 'BREX_CASH',
|
||||
id: params.cashAccountId,
|
||||
},
|
||||
}
|
||||
if (params.approvalType) body.approval_type = params.approvalType
|
||||
if (params.isPproEnabled !== undefined) body.is_ppro_enabled = params.isPproEnabled
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await parseBrexJson(response)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id ?? '',
|
||||
counterparty: data.counterparty ?? null,
|
||||
description: data.description ?? null,
|
||||
paymentType: data.payment_type ?? '',
|
||||
amount: data.amount ?? null,
|
||||
processDate: data.process_date ?? null,
|
||||
originatingAccount: data.originating_account ?? null,
|
||||
status: data.status ?? '',
|
||||
cancellationReason: data.cancellation_reason ?? null,
|
||||
estimatedDeliveryDate: data.estimated_delivery_date ?? null,
|
||||
creatorUserId: data.creator_user_id ?? null,
|
||||
createdAt: data.created_at ?? null,
|
||||
displayName: data.display_name ?? null,
|
||||
externalMemo: data.external_memo ?? null,
|
||||
isPproEnabled: data.is_ppro_enabled ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Unique transfer ID' },
|
||||
counterparty: { type: 'json', description: 'Transfer counterparty details', optional: true },
|
||||
description: { type: 'string', description: 'Description of the transfer', optional: true },
|
||||
paymentType: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Payment type (ACH, DOMESTIC_WIRE, CHEQUE, INTERNATIONAL_WIRE, BOOK_TRANSFER, STABLECOIN)',
|
||||
},
|
||||
amount: {
|
||||
type: 'json',
|
||||
description: 'Transfer amount',
|
||||
optional: true,
|
||||
properties: BREX_MONEY_PROPERTIES,
|
||||
},
|
||||
processDate: { type: 'string', description: 'Transaction processing date', optional: true },
|
||||
originatingAccount: {
|
||||
type: 'json',
|
||||
description: 'Originating account details for the transfer',
|
||||
optional: true,
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Transfer status (PROCESSING, SCHEDULED, PENDING_APPROVAL, FAILED, PROCESSED)',
|
||||
},
|
||||
cancellationReason: {
|
||||
type: 'string',
|
||||
description: 'Reason the transfer was canceled',
|
||||
optional: true,
|
||||
},
|
||||
estimatedDeliveryDate: {
|
||||
type: 'string',
|
||||
description: 'Estimated delivery date for the transfer',
|
||||
optional: true,
|
||||
},
|
||||
creatorUserId: {
|
||||
type: 'string',
|
||||
description: 'ID of the user who created the transfer',
|
||||
optional: true,
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
description: 'Creation timestamp of the transfer',
|
||||
optional: true,
|
||||
},
|
||||
displayName: {
|
||||
type: 'string',
|
||||
description: 'Human-readable name of the transfer',
|
||||
optional: true,
|
||||
},
|
||||
externalMemo: { type: 'string', description: 'External memo of the transfer', optional: true },
|
||||
isPproEnabled: {
|
||||
type: 'boolean',
|
||||
description: 'Whether Principal Protection (PPRO) is enabled for the transfer',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { generateId } from '@sim/utils/id'
|
||||
import type { BrexCreateVendorParams, BrexCreateVendorResponse } from '@/tools/brex/types'
|
||||
import { BREX_API_BASE, buildBrexHeaders, parseBrexJson } from '@/tools/brex/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const brexCreateVendorTool: ToolConfig<BrexCreateVendorParams, BrexCreateVendorResponse> = {
|
||||
id: 'brex_create_vendor',
|
||||
name: 'Brex Create Vendor',
|
||||
description: 'Create a new vendor in the Brex account',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Brex user token (generated from Developer Settings in the Brex dashboard)',
|
||||
},
|
||||
companyName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name for the vendor (must be unique)',
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Email address for the vendor',
|
||||
},
|
||||
phone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Phone number for the vendor',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => `${BREX_API_BASE}/v1/vendors`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
...buildBrexHeaders(params.apiKey),
|
||||
'Idempotency-Key': generateId(),
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = { company_name: params.companyName }
|
||||
if (params.email) body.email = params.email
|
||||
if (params.phone) body.phone = params.phone
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await parseBrexJson(response)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id ?? '',
|
||||
companyName: data.company_name ?? null,
|
||||
email: data.email ?? null,
|
||||
phone: data.phone ?? null,
|
||||
paymentAccounts: data.payment_accounts ?? [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Unique vendor ID' },
|
||||
companyName: { type: 'string', description: 'Vendor company name', optional: true },
|
||||
email: { type: 'string', description: 'Vendor email address', optional: true },
|
||||
phone: { type: 'string', description: 'Vendor phone number', optional: true },
|
||||
paymentAccounts: { type: 'array', description: 'Payment accounts associated with the vendor' },
|
||||
},
|
||||
}
|
||||
@@ -72,7 +72,7 @@ export const brexGetBudgetTool: ToolConfig<BrexGetBudgetParams, BrexGetBudgetRes
|
||||
},
|
||||
spendBudgetStatus: {
|
||||
type: 'string',
|
||||
description: 'Budget status (ACTIVE, ARCHIVED, DELETED, EXPIRED)',
|
||||
description: 'Budget status (ACTIVE, ARCHIVED, DELETED)',
|
||||
},
|
||||
limitType: { type: 'string', description: 'Budget limit type (HARD or SOFT)', optional: true },
|
||||
},
|
||||
|
||||
@@ -63,7 +63,7 @@ export const brexGetSpendLimitTool: ToolConfig<BrexGetSpendLimitParams, BrexGetS
|
||||
parentBudgetId: { type: 'string', description: 'Parent budget ID', optional: true },
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Spend limit status (ACTIVE, EXPIRED, ARCHIVED, DELETED)',
|
||||
description: 'Spend limit status (ACTIVE, EXPIRED, ARCHIVED)',
|
||||
},
|
||||
periodRecurrenceType: {
|
||||
type: 'string',
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
export { brexArchiveBudgetTool } from '@/tools/brex/archive_budget'
|
||||
export { brexCreateBudgetTool } from '@/tools/brex/create_budget'
|
||||
export { brexCreateSpendLimitTool } from '@/tools/brex/create_spend_limit'
|
||||
export { brexCreateTransferTool } from '@/tools/brex/create_transfer'
|
||||
export { brexCreateVendorTool } from '@/tools/brex/create_vendor'
|
||||
export { brexGetBudgetTool } from '@/tools/brex/get_budget'
|
||||
export { brexGetCashAccountTool } from '@/tools/brex/get_cash_account'
|
||||
export { brexGetCompanyTool } from '@/tools/brex/get_company'
|
||||
@@ -25,4 +30,5 @@ export { brexListUsersTool } from '@/tools/brex/list_users'
|
||||
export { brexListVendorsTool } from '@/tools/brex/list_vendors'
|
||||
export { brexMatchReceiptTool } from '@/tools/brex/match_receipt'
|
||||
export { brexUpdateExpenseTool } from '@/tools/brex/update_expense'
|
||||
export { brexUpdateVendorTool } from '@/tools/brex/update_vendor'
|
||||
export { brexUploadReceiptTool } from '@/tools/brex/upload_receipt'
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
BREX_API_BASE,
|
||||
buildBrexHeaders,
|
||||
parseBrexJson,
|
||||
toBrexDateTime,
|
||||
} from '@/tools/brex/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
@@ -87,8 +88,10 @@ export const brexListExpensesTool: ToolConfig<BrexListExpensesParams, BrexListEx
|
||||
appendBrexArrayParam(query, 'user_id[]', params.userIds)
|
||||
appendBrexArrayParam(query, 'status[]', params.statuses)
|
||||
appendBrexArrayParam(query, 'payment_status[]', params.paymentStatuses)
|
||||
if (params.purchasedAtStart) query.append('purchased_at_start', params.purchasedAtStart)
|
||||
if (params.purchasedAtEnd) query.append('purchased_at_end', params.purchasedAtEnd)
|
||||
if (params.purchasedAtStart)
|
||||
query.append('purchased_at_start', toBrexDateTime(params.purchasedAtStart))
|
||||
if (params.purchasedAtEnd)
|
||||
query.append('purchased_at_end', toBrexDateTime(params.purchasedAtEnd))
|
||||
appendBrexPagination(query, params)
|
||||
return `${BREX_API_BASE}/v1/expenses?${query.toString()}`
|
||||
},
|
||||
|
||||
@@ -299,6 +299,76 @@ export interface BrexGetTransferParams {
|
||||
transferId: string
|
||||
}
|
||||
|
||||
export interface BrexCreateTransferParams {
|
||||
apiKey: string
|
||||
cashAccountId: string
|
||||
vendorPaymentInstrumentId: string
|
||||
amount: number
|
||||
currency?: string
|
||||
description: string
|
||||
externalMemo: string
|
||||
approvalType?: string
|
||||
isPproEnabled?: boolean
|
||||
}
|
||||
|
||||
export interface BrexCreateBudgetParams {
|
||||
apiKey: string
|
||||
name: string
|
||||
description: string
|
||||
parentBudgetId: string
|
||||
periodRecurrenceType: string
|
||||
amount: number
|
||||
currency?: string
|
||||
ownerUserIds?: string
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
}
|
||||
|
||||
export interface BrexArchiveBudgetParams {
|
||||
apiKey: string
|
||||
budgetId: string
|
||||
}
|
||||
|
||||
export interface BrexCreateSpendLimitParams {
|
||||
apiKey: string
|
||||
name: string
|
||||
periodRecurrenceType: string
|
||||
spendType: string
|
||||
expenseVisibility: string
|
||||
authorizationVisibility: string
|
||||
limitIncreaseSetting: string
|
||||
autoTransferCardsSetting: string
|
||||
autoCreateLimitCardsSetting: string
|
||||
expensePolicyId: string
|
||||
baseLimitAmount: number
|
||||
currency?: string
|
||||
authorizationType: string
|
||||
rolloverRefreshRate: string
|
||||
limitBufferPercentage?: number
|
||||
description?: string
|
||||
parentBudgetId?: string
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
transactionLimitAmount?: number
|
||||
ownerUserIds?: string
|
||||
memberUserIds?: string
|
||||
}
|
||||
|
||||
export interface BrexCreateVendorParams {
|
||||
apiKey: string
|
||||
companyName: string
|
||||
email?: string
|
||||
phone?: string
|
||||
}
|
||||
|
||||
export interface BrexUpdateVendorParams {
|
||||
apiKey: string
|
||||
vendorId: string
|
||||
companyName?: string
|
||||
email?: string
|
||||
phone?: string
|
||||
}
|
||||
|
||||
export interface BrexListExpensesResponse extends ToolResponse {
|
||||
output: {
|
||||
items: BrexExpense[]
|
||||
@@ -559,6 +629,89 @@ export interface BrexGetTransferResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export interface BrexCreateTransferResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
counterparty: Record<string, unknown> | null
|
||||
description: string | null
|
||||
paymentType: string
|
||||
amount: BrexMoney | null
|
||||
processDate: string | null
|
||||
originatingAccount: Record<string, unknown> | null
|
||||
status: string
|
||||
cancellationReason: string | null
|
||||
estimatedDeliveryDate: string | null
|
||||
creatorUserId: string | null
|
||||
createdAt: string | null
|
||||
displayName: string | null
|
||||
externalMemo: string | null
|
||||
isPproEnabled: boolean | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface BrexCreateBudgetResponse extends ToolResponse {
|
||||
output: {
|
||||
budgetId: string
|
||||
accountId: string
|
||||
name: string
|
||||
description: string | null
|
||||
parentBudgetId: string | null
|
||||
ownerUserIds: string[]
|
||||
periodRecurrenceType: string
|
||||
startDate: string | null
|
||||
endDate: string | null
|
||||
amount: BrexMoney | null
|
||||
spendBudgetStatus: string
|
||||
limitType: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface BrexArchiveBudgetResponse extends ToolResponse {
|
||||
output: {
|
||||
budgetId: string
|
||||
spendBudgetStatus: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface BrexCreateSpendLimitResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
accountId: string
|
||||
name: string
|
||||
description: string | null
|
||||
parentBudgetId: string | null
|
||||
status: string
|
||||
periodRecurrenceType: string
|
||||
spendType: string
|
||||
startDate: string | null
|
||||
endDate: string | null
|
||||
ownerUserIds: string[]
|
||||
memberUserIds: string[]
|
||||
currentPeriodBalance: BrexSpendLimitPeriodBalance | null
|
||||
authorizationSettings: Record<string, unknown> | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface BrexCreateVendorResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
companyName: string | null
|
||||
email: string | null
|
||||
phone: string | null
|
||||
paymentAccounts: unknown[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface BrexUpdateVendorResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
companyName: string | null
|
||||
email: string | null
|
||||
phone: string | null
|
||||
paymentAccounts: unknown[]
|
||||
}
|
||||
}
|
||||
|
||||
export type BrexResponse =
|
||||
| BrexListExpensesResponse
|
||||
| BrexGetExpenseResponse
|
||||
@@ -585,6 +738,12 @@ export type BrexResponse =
|
||||
| BrexGetSpendLimitResponse
|
||||
| BrexGetVendorResponse
|
||||
| BrexGetTransferResponse
|
||||
| BrexCreateTransferResponse
|
||||
| BrexCreateBudgetResponse
|
||||
| BrexArchiveBudgetResponse
|
||||
| BrexCreateSpendLimitResponse
|
||||
| BrexCreateVendorResponse
|
||||
| BrexUpdateVendorResponse
|
||||
|
||||
export const BREX_MONEY_PROPERTIES: Record<string, OutputProperty> = {
|
||||
amount: {
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { generateId } from '@sim/utils/id'
|
||||
import type { BrexUpdateVendorParams, BrexUpdateVendorResponse } from '@/tools/brex/types'
|
||||
import { BREX_API_BASE, buildBrexHeaders, parseBrexJson } from '@/tools/brex/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const brexUpdateVendorTool: ToolConfig<BrexUpdateVendorParams, BrexUpdateVendorResponse> = {
|
||||
id: 'brex_update_vendor',
|
||||
name: 'Brex Update Vendor',
|
||||
description: 'Update an existing vendor in the Brex account',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Brex user token (generated from Developer Settings in the Brex dashboard)',
|
||||
},
|
||||
vendorId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the vendor to update',
|
||||
},
|
||||
companyName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New name for the vendor',
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New email address for the vendor',
|
||||
},
|
||||
phone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New phone number for the vendor',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `${BREX_API_BASE}/v1/vendors/${encodeURIComponent(params.vendorId.trim())}`,
|
||||
method: 'PUT',
|
||||
headers: (params) => ({
|
||||
...buildBrexHeaders(params.apiKey),
|
||||
// Optional per Brex's spec for this endpoint, but included for safe-retry semantics.
|
||||
'Idempotency-Key': generateId(),
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {}
|
||||
if (params.companyName) body.company_name = params.companyName
|
||||
if (params.email) body.email = params.email
|
||||
if (params.phone) body.phone = params.phone
|
||||
if (Object.keys(body).length === 0) {
|
||||
throw new Error(
|
||||
'At least one of company name, email, or phone must be provided to update the vendor'
|
||||
)
|
||||
}
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await parseBrexJson(response)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id ?? '',
|
||||
companyName: data.company_name ?? null,
|
||||
email: data.email ?? null,
|
||||
phone: data.phone ?? null,
|
||||
paymentAccounts: data.payment_accounts ?? [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Unique vendor ID' },
|
||||
companyName: { type: 'string', description: 'Vendor company name', optional: true },
|
||||
email: { type: 'string', description: 'Vendor email address', optional: true },
|
||||
phone: { type: 'string', description: 'Vendor phone number', optional: true },
|
||||
paymentAccounts: { type: 'array', description: 'Payment accounts associated with the vendor' },
|
||||
},
|
||||
}
|
||||
@@ -51,6 +51,19 @@ export function appendBrexPagination(
|
||||
if (params.limit) query.append('limit', params.limit)
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a comma-separated string of IDs into a trimmed, non-empty array for
|
||||
* use in a JSON request body (as opposed to repeated query parameters).
|
||||
*/
|
||||
export function splitBrexIdList(value?: string): string[] | undefined {
|
||||
if (!value) return undefined
|
||||
const ids = value
|
||||
.split(',')
|
||||
.map((id) => id.trim())
|
||||
.filter(Boolean)
|
||||
return ids.length > 0 ? ids : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a timestamp to the timezone-less date-time form the Brex Transactions
|
||||
* API requires (e.g., 2026-01-01T00:00:00). Brex rejects timezone-suffixed
|
||||
|
||||
@@ -345,6 +345,11 @@ import {
|
||||
} from '@/tools/box_sign'
|
||||
import { brandfetchGetBrandTool, brandfetchSearchTool } from '@/tools/brandfetch'
|
||||
import {
|
||||
brexArchiveBudgetTool,
|
||||
brexCreateBudgetTool,
|
||||
brexCreateSpendLimitTool,
|
||||
brexCreateTransferTool,
|
||||
brexCreateVendorTool,
|
||||
brexGetBudgetTool,
|
||||
brexGetCashAccountTool,
|
||||
brexGetCompanyTool,
|
||||
@@ -372,6 +377,7 @@ import {
|
||||
brexListVendorsTool,
|
||||
brexMatchReceiptTool,
|
||||
brexUpdateExpenseTool,
|
||||
brexUpdateVendorTool,
|
||||
brexUploadReceiptTool,
|
||||
} from '@/tools/brex'
|
||||
import {
|
||||
@@ -4632,6 +4638,11 @@ export const tools: Record<string, ToolConfig> = {
|
||||
athena_stop_query: athenaStopQueryTool,
|
||||
brandfetch_get_brand: brandfetchGetBrandTool,
|
||||
brandfetch_search: brandfetchSearchTool,
|
||||
brex_archive_budget: brexArchiveBudgetTool,
|
||||
brex_create_budget: brexCreateBudgetTool,
|
||||
brex_create_spend_limit: brexCreateSpendLimitTool,
|
||||
brex_create_transfer: brexCreateTransferTool,
|
||||
brex_create_vendor: brexCreateVendorTool,
|
||||
brex_get_budget: brexGetBudgetTool,
|
||||
brex_get_cash_account: brexGetCashAccountTool,
|
||||
brex_get_company: brexGetCompanyTool,
|
||||
@@ -4659,6 +4670,7 @@ export const tools: Record<string, ToolConfig> = {
|
||||
brex_list_vendors: brexListVendorsTool,
|
||||
brex_match_receipt: brexMatchReceiptTool,
|
||||
brex_update_expense: brexUpdateExpenseTool,
|
||||
brex_update_vendor: brexUpdateVendorTool,
|
||||
brex_upload_receipt: brexUploadReceiptTool,
|
||||
brightdata_cancel_snapshot: brightDataCancelSnapshotTool,
|
||||
brightdata_discover: brightDataDiscoverTool,
|
||||
|
||||
Reference in New Issue
Block a user