Skip to main content
Glama
multi-select-transformer.ts5.3 kB
/** * Multi-select transformer - wraps single values in arrays for multi-select fields * * Problem: LLMs commonly pass multi-select values as single strings (e.g., "Inbound") * but Attio API requires array format (e.g., ["Inbound"]). * * Solution: Auto-detect multi-select attributes and wrap single values in arrays. */ import { TransformContext, TransformResult, AttributeMetadata, } from './types.js'; import { debug, OperationType } from '@/utils/logger.js'; /** * Multi-select attribute types that require array values (legacy type names) */ const MULTI_SELECT_TYPES = ['multi_select', 'multi-select', 'multiselect']; /** * Check if an attribute type name indicates multi-select (legacy check) * @internal Use isMultiSelectAttribute() for proper detection */ export function isMultiSelectTypeName(type: string): boolean { return MULTI_SELECT_TYPES.includes(type.toLowerCase().replace(/[_-]/g, '')); } /** * Check if an attribute is multi-select based on metadata * * CRITICAL: Attio's actual API format for multi-select attributes is: * { type: "select", is_multiselect: true } * * NOT { type: "multi_select" } as one might expect. * * This function checks the is_multiselect flag FIRST because: * 1. Custom workspace attributes (e.g., lead_type, inbound_outbound) use type="select" * 2. The is_multiselect flag is the authoritative source from Attio's API * 3. Type name matching is only for backward compatibility with older schemas * * Issue #992: Without flag-first detection, custom multi-select attributes were * not being auto-wrapped to arrays, causing API errors. * * @param meta - Attribute metadata from Attio API * @returns true if the attribute accepts multiple values */ export function isMultiSelectAttribute(meta: AttributeMetadata): boolean { // IMPORTANT: Check is_multiselect flag first - this is Attio's authoritative format // Custom workspace attributes have type="select" but is_multiselect=true if (meta.is_multiselect === true) { return true; } // Fallback to type name matching for backward compatibility only // (handles edge cases where schema lacks the is_multiselect field) return isMultiSelectTypeName(meta.type); } /** * Check if an attribute type is multi-select * @deprecated Use isMultiSelectAttribute() instead for proper detection with metadata */ export function isMultiSelectType(type: string): boolean { return isMultiSelectTypeName(type); } /** * Check if a value is already an array */ function isArray(value: unknown): value is unknown[] { return Array.isArray(value); } /** * Transform a multi-select value from single string to array format * * @param value - The value to transform * @param attributeSlug - The attribute slug * @param context - Transformation context * @param attributeMeta - Attribute metadata * @returns Transform result */ export async function transformMultiSelectValue( value: unknown, attributeSlug: string, context: TransformContext, attributeMeta: AttributeMetadata ): Promise<TransformResult> { // Only transform multi-select attributes (check is_multiselect flag OR type name) if (!isMultiSelectAttribute(attributeMeta)) { return { transformed: false, originalValue: value, transformedValue: value, }; } // Skip if already an array if (isArray(value)) { return { transformed: false, originalValue: value, transformedValue: value, }; } // Skip null/undefined values if (value === null || value === undefined) { return { transformed: false, originalValue: value, transformedValue: value, }; } // Wrap single value in array const transformedValue = [value]; debug( 'multi-select-transformer', `Wrapped single value in array for multi-select`, { attribute: attributeSlug, from: value, to: transformedValue, resourceType: context.resourceType, detectedVia: attributeMeta.is_multiselect ? 'is_multiselect flag' : 'type name', }, 'transformMultiSelectValue', OperationType.DATA_PROCESSING ); return { transformed: true, originalValue: value, transformedValue, description: `Wrapped single value "${value}" in array for multi-select field ${attributeSlug}`, }; } /** * Check if a value needs to be wrapped for multi-select * (Useful for validation without transformation) * @deprecated Use needsArrayWrappingForAttribute() instead for proper detection */ export function needsArrayWrapping( value: unknown, attributeType: string ): boolean { if (!isMultiSelectTypeName(attributeType)) return false; if (isArray(value)) return false; if (value === null || value === undefined) return false; return true; } /** * Check if a value needs to be wrapped for multi-select based on attribute metadata * @param value - The value to check * @param attributeMeta - Attribute metadata * @returns true if the value needs to be wrapped in an array */ export function needsArrayWrappingForAttribute( value: unknown, attributeMeta: AttributeMetadata ): boolean { if (!isMultiSelectAttribute(attributeMeta)) return false; if (isArray(value)) return false; if (value === null || value === undefined) return false; return true; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kesslerio/attio-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server