UNPKG

@coretext-ai/custom-test-google-contacts-a58500d5-8331-4ce9-a140-d204a9fae815

Version:
2,153 lines 106 kB
import axios from 'axios';
import { Logger } from '../services/logger.js';
export class GoogleContactsClient {
    constructor(config) {
        this.config = config;
        // Generate unique session ID for this client instance
        this.sessionId = `google-contacts-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
        // Initialize logger (fallback to console if not provided)
        this.logger = config.logger || new Logger({
            logLevel: 'ERROR',
            component: 'client',
            enableConsole: true,
            enableShipping: false,
            serverName: 'google-contacts-mcp-server'
        });
        this.logger.info('CLIENT_INIT', 'Client instance created', {
            baseUrl: this.resolveBaseUrl(),
            timeout: this.config.timeout || 30000,
            hasRateLimit: !!this.config.rateLimit,
            configKeys: Object.keys(config)
        });
        // Initialize OAuth client from config if provided
        this.oauthClient = config.oauthClient;
        this.httpClient = axios.create({
            baseURL: this.resolveBaseUrl(),
            timeout: this.config.timeout || 30000,
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json',
                'User-Agent': 'google-contacts-mcp-server/1.0.0',
                ...this.getAuthHeaders()
            },
        });
        // Add request interceptor for rate limiting
        if (this.config.rateLimit) {
            this.setupRateLimit(this.config.rateLimit);
        }
        // Add request interceptor for logging
        this.httpClient.interceptors.request.use((config) => {
            this.logger.logRequestStart(config.method?.toUpperCase() || 'GET', `${config.baseURL}${config.url}`, {
                hasData: !!config.data,
                hasParams: !!(config.params && Object.keys(config.params).length > 0),
                headers: Object.keys(config.headers || {})
            });
            if (config.data) {
                this.logger.debug('HTTP_REQUEST_BODY', 'Request body data', {
                    dataType: typeof config.data,
                    dataSize: JSON.stringify(config.data).length
                });
            }
            if (config.params && Object.keys(config.params).length > 0) {
                this.logger.debug('HTTP_REQUEST_PARAMS', 'Query parameters', {
                    paramCount: Object.keys(config.params).length,
                    paramKeys: Object.keys(config.params)
                });
            }
            return config;
        }, (error) => {
            this.logger.error('HTTP_REQUEST_ERROR', 'Request interceptor error', {
                error: error.message,
                code: error.code
            });
            return Promise.reject(error);
        });
        // Add response interceptor for logging and error handling
        this.httpClient.interceptors.response.use((response) => {
            this.logger.logRequestSuccess(response.config?.method?.toUpperCase() || 'GET', `${response.config?.baseURL}${response.config?.url}`, response.status, 0, // Duration will be calculated in endpoint methods
            {
                statusText: response.statusText,
                responseSize: JSON.stringify(response.data).length,
                headers: Object.keys(response.headers || {})
            });
            return response;
        }, (error) => {
            this.logger.logRequestError(error.config?.method?.toUpperCase() || 'GET', `${error.config?.baseURL}${error.config?.url}`, error, 0, // Duration will be calculated in endpoint methods
            {
                hasResponseData: !!error.response?.data
            });
            throw error;
        });
    }
    setupRateLimit(requestsPerMinute) {
        const interval = 60000 / requestsPerMinute; // ms between requests
        let lastRequestTime = 0;
        this.logger.info('RATE_LIMIT_SETUP', 'Rate limiting configured', {
            requestsPerMinute,
            intervalMs: interval
        });
        this.httpClient.interceptors.request.use(async (config) => {
            const now = Date.now();
            const timeSinceLastRequest = now - lastRequestTime;
            if (timeSinceLastRequest < interval) {
                const delayMs = interval - timeSinceLastRequest;
                this.logger.logRateLimit('HTTP_REQUEST', delayMs, {
                    timeSinceLastRequest,
                    requiredInterval: interval
                });
                await new Promise(resolve => setTimeout(resolve, delayMs));
            }
            lastRequestTime = Date.now();
            return config;
        });
    }
    resolveBaseUrl() {
        let baseUrl = 'https://people.googleapis.com';
        // Handle dynamic domain replacement (e.g., YOUR_DOMAIN placeholder)
        if (baseUrl.includes('YOUR_DOMAIN')) {
            const domainEnvVar = `GOOGLE_CONTACTS_DOMAIN`;
            const domain = process.env[domainEnvVar];
            if (!domain) {
                throw new Error(`Missing domain configuration. Please set ${domainEnvVar} environment variable.`);
            }
            baseUrl = baseUrl.replace('YOUR_DOMAIN', domain);
            console.error(`[GOOGLE_CONTACTS] Resolved base URL: ${baseUrl}`);
        }
        return baseUrl;
    }
    getAuthHeaders() {
        // OAuth authentication (both ConstructionWire and standard OAuth) - handled dynamically
        // Tokens will be applied asynchronously via makeAuthenticatedRequest
        this.logger.logAuthEvent('oauth_auth_setup', true, {
            authType: 'oauth2',
            message: 'OAuth tokens will be applied dynamically during requests',
            oauthClientPresent: !!this.oauthClient
        });
        return {};
    }
    /**
     * Initialize the client (for OAuth clients that need initialization)
     */
    async initialize() {
        if (this.oauthClient) {
            await this.oauthClient.initialize();
            this.logger.info('CLIENT_INITIALIZE', 'OAuth client initialized');
        }
    }
    /**
     * Get the session ID for this client instance
     */
    getSessionId() {
        return this.sessionId;
    }
    /**
     * Make an authenticated request with proper headers
     */
    async makeAuthenticatedRequest(config) {
        // Get OAuth token for standard OAuth
        this.logger.info('REQUEST_AUTH', 'Applying standard OAuth authentication', {
            authType: 'oauth2',
            requestUrl: config.url,
            hasOAuthClient: !!this.oauthClient
        });
        if (this.oauthClient) {
            const accessToken = await this.oauthClient.getValidAccessToken();
            config.headers = {
                ...config.headers,
                'Authorization': `Bearer ${accessToken}`
            };
            this.logger.logAuthEvent('oauth_token_applied', true, {
                authType: 'oauth2',
                tokenPreview: accessToken ? accessToken.substring(0, 8) + '...' : 'null',
                header: 'Authorization',
                tokenSource: 'standard_oauth',
                finalHeaders: Object.keys(config.headers)
            });
        }
        else {
            this.logger.warn('OAUTH_CLIENT_MISSING', 'OAuth client not available for OAuth-enabled template', {
                authType: 'oauth2',
                requestUrl: config.url
            });
        }
        return this.httpClient.request(config);
    }
    buildPath(template, params) {
        let path = template;
        // Custom encoding that preserves forward slashes for API paths
        const encodePathComponent = (value) => {
            // For Google API resource names like "people/c123", preserve the forward slash
            return encodeURIComponent(value).replace(/%2F/g, '/');
        };
        // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
        const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
        let match;
        const processedParams = [];
        while ((match = googlePathTemplateRegex.exec(template)) !== null) {
            const fullMatch = match[0]; // e.g., "{resourceName=people/*}"
            const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
            if (paramName && params[paramName] !== undefined) {
                path = path.replace(fullMatch, encodePathComponent(String(params[paramName])));
                processedParams.push(paramName);
            }
        }
        // Handle standard path templates: {resourceName}
        for (const [key, value] of Object.entries(params)) {
            if (!processedParams.includes(key)) {
                const standardTemplate = `{${key}}`;
                if (path.includes(standardTemplate)) {
                    path = path.replace(standardTemplate, encodePathComponent(String(value)));
                    processedParams.push(key);
                }
            }
        }
        this.logger.debug('PATH_BUILD', 'Built API path from template', {
            template,
            resultPath: path,
            paramCount: Object.keys(params).length,
            paramKeys: Object.keys(params),
            processedParams,
            hasGoogleTemplates: googlePathTemplateRegex.test(template)
        });
        return path;
    }
    async createContact(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'create_contact',
            method: 'POST',
            path: '/v1/people:createContact',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/v1/people:createContact';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["personFields"] !== undefined) {
                queryParams["personFields"] = params["personFields"];
                extractedParams.push("personFields");
            }
            if (params["sources"] !== undefined) {
                queryParams["sources"] = params["sources"];
                extractedParams.push("sources");
            }
            if (params["names"] !== undefined) {
                bodyParams["names"] = params["names"];
                extractedParams.push("names");
            }
            if (params["emailAddresses"] !== undefined) {
                bodyParams["emailAddresses"] = params["emailAddresses"];
                extractedParams.push("emailAddresses");
            }
            if (params["phoneNumbers"] !== undefined) {
                bodyParams["phoneNumbers"] = params["phoneNumbers"];
                extractedParams.push("phoneNumbers");
            }
            if (params["addresses"] !== undefined) {
                bodyParams["addresses"] = params["addresses"];
                extractedParams.push("addresses");
            }
            if (params["organizations"] !== undefined) {
                bodyParams["organizations"] = params["organizations"];
                extractedParams.push("organizations");
            }
            if (params["biographies"] !== undefined) {
                bodyParams["biographies"] = params["biographies"];
                extractedParams.push("biographies");
            }
            if (params["birthdays"] !== undefined) {
                bodyParams["birthdays"] = params["birthdays"];
                extractedParams.push("birthdays");
            }
            if (params["urls"] !== undefined) {
                bodyParams["urls"] = params["urls"];
                extractedParams.push("urls");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.personFields) {
                throw new Error(`Missing required parameter: personFields`);
            }
            const path = this.buildPath('/v1/people:createContact', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'POST', url: path, params: queryParams, data: Object.keys(bodyParams).length > 0 ? bodyParams : undefined });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'create_contact',
                method: 'POST',
                path: '/v1/people:createContact',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'create_contact',
                method: 'POST',
                path: '/v1/people:createContact',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute create_contact: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async getPerson(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'get_person',
            method: 'GET',
            path: '/v1/{resourceName=people/*}',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/v1/{resourceName=people/*}';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["personFields"] !== undefined) {
                queryParams["personFields"] = params["personFields"];
                extractedParams.push("personFields");
            }
            if (params["sources"] !== undefined) {
                queryParams["sources"] = params["sources"];
                extractedParams.push("sources");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.resourceName) {
                throw new Error(`Missing required parameter: resourceName`);
            }
            if (!params.personFields) {
                throw new Error(`Missing required parameter: personFields`);
            }
            const path = this.buildPath('/v1/{resourceName=people/*}', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'GET', url: path, params: queryParams });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'get_person',
                method: 'GET',
                path: '/v1/{resourceName=people/*}',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'get_person',
                method: 'GET',
                path: '/v1/{resourceName=people/*}',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute get_person: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async updateContact(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'update_contact',
            method: 'PATCH',
            path: '/v1/{person.resourceName=people/*}:updateContact',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/v1/{person.resourceName=people/*}:updateContact';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["updatePersonFields"] !== undefined) {
                queryParams["updatePersonFields"] = params["updatePersonFields"];
                extractedParams.push("updatePersonFields");
            }
            if (params["personFields"] !== undefined) {
                queryParams["personFields"] = params["personFields"];
                extractedParams.push("personFields");
            }
            if (params["sources"] !== undefined) {
                queryParams["sources"] = params["sources"];
                extractedParams.push("sources");
            }
            if (params["names"] !== undefined) {
                bodyParams["names"] = params["names"];
                extractedParams.push("names");
            }
            if (params["emailAddresses"] !== undefined) {
                bodyParams["emailAddresses"] = params["emailAddresses"];
                extractedParams.push("emailAddresses");
            }
            if (params["phoneNumbers"] !== undefined) {
                bodyParams["phoneNumbers"] = params["phoneNumbers"];
                extractedParams.push("phoneNumbers");
            }
            if (params["addresses"] !== undefined) {
                bodyParams["addresses"] = params["addresses"];
                extractedParams.push("addresses");
            }
            if (params["organizations"] !== undefined) {
                bodyParams["organizations"] = params["organizations"];
                extractedParams.push("organizations");
            }
            if (params["biographies"] !== undefined) {
                bodyParams["biographies"] = params["biographies"];
                extractedParams.push("biographies");
            }
            if (params["birthdays"] !== undefined) {
                bodyParams["birthdays"] = params["birthdays"];
                extractedParams.push("birthdays");
            }
            if (params["urls"] !== undefined) {
                bodyParams["urls"] = params["urls"];
                extractedParams.push("urls");
            }
            if (params["etag"] !== undefined) {
                bodyParams["etag"] = params["etag"];
                extractedParams.push("etag");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params["person.resourceName"]) {
                throw new Error(`Missing required parameter: person.resourceName`);
            }
            if (!params.updatePersonFields) {
                throw new Error(`Missing required parameter: updatePersonFields`);
            }
            if (!params.etag) {
                throw new Error(`Missing required parameter: etag`);
            }
            const path = this.buildPath('/v1/{person.resourceName=people/*}:updateContact', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'PATCH', url: path, params: queryParams, data: Object.keys(bodyParams).length > 0 ? bodyParams : undefined });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'update_contact',
                method: 'PATCH',
                path: '/v1/{person.resourceName=people/*}:updateContact',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'update_contact',
                method: 'PATCH',
                path: '/v1/{person.resourceName=people/*}:updateContact',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute update_contact: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async deleteContact(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'delete_contact',
            method: 'DELETE',
            path: '/v1/{resourceName=people/*}:deleteContact',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/v1/{resourceName=people/*}:deleteContact';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.resourceName) {
                throw new Error(`Missing required parameter: resourceName`);
            }
            const path = this.buildPath('/v1/{resourceName=people/*}:deleteContact', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'DELETE', url: path, params: queryParams });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'delete_contact',
                method: 'DELETE',
                path: '/v1/{resourceName=people/*}:deleteContact',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'delete_contact',
                method: 'DELETE',
                path: '/v1/{resourceName=people/*}:deleteContact',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute delete_contact: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async listConnections(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'list_connections',
            method: 'GET',
            path: '/v1/people/me/connections',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/v1/people/me/connections';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["pageSize"] !== undefined) {
                queryParams["pageSize"] = params["pageSize"];
                extractedParams.push("pageSize");
            }
            if (params["pageToken"] !== undefined) {
                queryParams["pageToken"] = params["pageToken"];
                extractedParams.push("pageToken");
            }
            if (params["personFields"] !== undefined) {
                queryParams["personFields"] = params["personFields"];
                extractedParams.push("personFields");
            }
            if (params["sortOrder"] !== undefined) {
                queryParams["sortOrder"] = params["sortOrder"];
                extractedParams.push("sortOrder");
            }
            if (params["sources"] !== undefined) {
                queryParams["sources"] = params["sources"];
                extractedParams.push("sources");
            }
            if (params["syncToken"] !== undefined) {
                queryParams["syncToken"] = params["syncToken"];
                extractedParams.push("syncToken");
            }
            if (params["requestSyncToken"] !== undefined) {
                queryParams["requestSyncToken"] = params["requestSyncToken"];
                extractedParams.push("requestSyncToken");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.personFields) {
                throw new Error(`Missing required parameter: personFields`);
            }
            const path = this.buildPath('/v1/people/me/connections', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'GET', url: path, params: queryParams });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'list_connections',
                method: 'GET',
                path: '/v1/people/me/connections',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'list_connections',
                method: 'GET',
                path: '/v1/people/me/connections',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute list_connections: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async searchContacts(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'search_contacts',
            method: 'GET',
            path: '/v1/people:searchContacts',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/v1/people:searchContacts';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["query"] !== undefined) {
                queryParams["query"] = params["query"];
                extractedParams.push("query");
            }
            if (params["pageSize"] !== undefined) {
                queryParams["pageSize"] = params["pageSize"];
                extractedParams.push("pageSize");
            }
            if (params["readMask"] !== undefined) {
                queryParams["readMask"] = params["readMask"];
                extractedParams.push("readMask");
            }
            if (params["sources"] !== undefined) {
                queryParams["sources"] = params["sources"];
                extractedParams.push("sources");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.query) {
                throw new Error(`Missing required parameter: query`);
            }
            const path = this.buildPath('/v1/people:searchContacts', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'GET', url: path, params: queryParams });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'search_contacts',
                method: 'GET',
                path: '/v1/people:searchContacts',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'search_contacts',
                method: 'GET',
                path: '/v1/people:searchContacts',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute search_contacts: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async batchGetPeople(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'batch_get_people',
            method: 'GET',
            path: '/v1/people:batchGet',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/v1/people:batchGet';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["resourceNames"] !== undefined) {
                queryParams["resourceNames"] = params["resourceNames"];
                extractedParams.push("resourceNames");
            }
            if (params["personFields"] !== undefined) {
                queryParams["personFields"] = params["personFields"];
                extractedParams.push("personFields");
            }
            if (params["sources"] !== undefined) {
                queryParams["sources"] = params["sources"];
                extractedParams.push("sources");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.resourceNames) {
                throw new Error(`Missing required parameter: resourceNames`);
            }
            if (!params.personFields) {
                throw new Error(`Missing required parameter: personFields`);
            }
            const path = this.buildPath('/v1/people:batchGet', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'GET', url: path, params: queryParams });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'batch_get_people',
                method: 'GET',
                path: '/v1/people:batchGet',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'batch_get_people',
                method: 'GET',
                path: '/v1/people:batchGet',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute batch_get_people: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async listContactGroups(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'list_contact_groups',
            method: 'GET',
            path: '/v1/contactGroups',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/v1/contactGroups';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["pageSize"] !== undefined) {
                queryParams["pageSize"] = params["pageSize"];
                extractedParams.push("pageSize");
            }
            if (params["pageToken"] !== undefined) {
                queryParams["pageToken"] = params["pageToken"];
                extractedParams.push("pageToken");
            }
            if (params["groupFields"] !== undefined) {
                queryParams["groupFields"] = params["groupFields"];
                extractedParams.push("groupFields");
            }
            if (params["syncToken"] !== undefined) {
                queryParams["syncToken"] = params["syncToken"];
                extractedParams.push("syncToken");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            const path = this.buildPath('/v1/contactGroups', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'GET', url: path, params: queryParams });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'list_contact_groups',
                method: 'GET',
                path: '/v1/contactGroups',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'list_contact_groups',
                method: 'GET',
                path: '/v1/contactGroups',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute list_contact_groups: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async createContactGroup(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'create_contact_group',
            method: 'POST',
            path: '/v1/contactGroups',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/v1/contactGroups';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["contactGroup"] !== undefined) {
                bodyParams["contactGroup"] = params["contactGroup"];
                extractedParams.push("contactGroup");
            }
            if (params["readGroupFields"] !== undefined) {
                queryParams["readGroupFields"] = params["readGroupFields"];
                extractedParams.push("readGroupFields");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.contactGroup) {
                throw new Error(`Missing required parameter: contactGroup`);
            }
            const path = this.buildPath('/v1/contactGroups', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'POST', url: path, params: queryParams, data: Object.keys(bodyParams).length > 0 ? bodyParams : undefined });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'create_contact_group',
                method: 'POST',
                path: '/v1/contactGroups',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'create_contact_group',
                method: 'POST',
                path: '/v1/contactGroups',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute create_contact_group: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async getContactGroup(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'get_contact_group',
            method: 'GET',
            path: '/v1/{resourceName=contactGroups/*}',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/v1/{resourceName=contactGroups/*}';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["maxMembers"] !== undefined) {
                queryParams["maxMembers"] = params["maxMembers"];
                extractedParams.push("maxMembers");
            }
            if (params["groupFields"] !== undefined) {
                queryParams["groupFields"] = params["groupFields"];
                extractedParams.push("groupFields");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.resourceName) {
                throw new Error(`Missing required parameter: resourceName`);
            }
            const path = this.buildPath('/v1/{resourceName=contactGroups/*}', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'GET', url: path, params: queryParams });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'get_contact_group',
                method: 'GET',
                path: '/v1/{resourceName=contactGroups/*}',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'get_contact_group',
                method: 'GET',
                path: '/v1/{resourceName=contactGroups/*}',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute get_contact_group: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async updateContactGroup(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'update_contact_group',
            method: 'PUT',
            path: '/v1/{resourceName=contactGroups/*}',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/v1/{resourceName=contactGroups/*}';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["contactGroup"] !== undefined) {
                bodyParams["contactGroup"] = params["contactGroup"];
                extractedParams.push("contactGroup");
            }
            if (params["readGroupFields"] !== undefined) {
                queryParams["readGroupFields"] = params["readGroupFields"];
                extractedParams.push("readGroupFields");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.resourceName) {
                throw new Error(`Missing required parameter: resourceName`);
            }
            if (!params.contactGroup) {
                throw new Error(`Missing required parameter: contactGroup`);
            }
            const path = this.buildPath('/v1/{resourceName=contactGroups/*}', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'PUT', url: path, params: queryParams, data: Object.keys(bodyParams).length > 0 ? bodyParams : undefined });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'update_contact_group',
                method: 'PUT',
                path: '/v1/{resourceName=contactGroups/*}',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'update_contact_group',
                method: 'PUT',
                path: '/v1/{resourceName=contactGroups/*}',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute update_contact_group: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async deleteContactGroup(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'delete_contact_group',
            method: 'DELETE',
            path: '/v1/{resourceName=contactGroups/*}',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/v1/{resourceName=contactGroups/*}';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["deleteContacts"] !== undefined) {
                queryParams["deleteContacts"] = params["deleteContacts"];
                extractedParams.push("deleteContacts");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.resourceName) {
                throw new Error(`Missing required parameter: resourceName`);
            }
            const path = this.buildPath('/v1/{resourceName=contactGroups/*}', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'DELETE', url: path, params: queryParams });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'delete_contact_group',
                method: 'DELETE',
                path: '/v1/{resourceName=contactGroups/*}',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'delete_contact_group',
                method: 'DELETE',
                path: '/v1/{resourceName=contactGroups/*}',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute delete_contact_group: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async modifyContactGroupMembers(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'modify_contact_group_members',
            method: 'POST',
            path: '/v1/{resourceName=contactGroups/*}/members:modify',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/v1/{resourceName=contactGroups/*}/members:modify';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["resourceNamesToAdd"] !== undefined) {
                bodyParams["resourceNamesToAdd"] = params["resourceNamesToAdd"];
                extractedParams.push("resourceNamesToAdd");
            }
            if (params["resourceNamesToRemove"] !== undefined) {
                bodyParams["resourceNamesToRemove"] = params["resourceNamesToRemove"];
                extractedParams.push("resourceNamesToRemove");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.resourceName) {
                throw new Error(`Missing required parameter: resourceName`);
            }
            const path = this.buildPath('/v1/{resourceName=contactGroups/*}/members:modify', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'POST', url: path, params: queryParams, data: Object.keys(bodyParams).length > 0 ? bodyParams : undefined });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'modify_contact_group_members',
                method: 'POST',
                path: '/v1/{resourceName=contactGroups/*}/members:modify',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'modify_contact_group_members',
                method: 'POST',
                path: '/v1/{resourceName=contactGroups/*}/members:modify',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute modify_contact_group_members: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async listDirectoryPeople(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'list_directory_people',
            method: 'GET',
            path: '/v1/people:listDirectoryPeople',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/v1/people:listDirectoryPeople';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["pageSize"] !== undefined) {
                queryParams["pageSize"] = params["pageSize"];
                extractedParams.push("pageSize");
            }
            if (params["pageToken"] !== undefined) {
                queryParams["pageToken"] = params["pageToken"];
                extractedParams.push("pageToken");
            }
            if (params["readMask"] !== undefined) {
                queryParams["readMask"] = params["readMask"];
                extractedParams.push("readMask");
            }
            if (params["sources"] !== undefined) {
                // Handle array parameters - convert to comma-separated string for Google APIs
                queryParams["sources"] = Array.isArray(params["sources"])
                    ? params["sources"].join(',')
                    : params["sources"];
                extractedParams.push("sources");
            }
            if (params["mergeSources"] !== undefined) {
                // Handle array parameters - convert to comma-separated string for Google APIs
                queryParams["mergeSources"] = Array.isArray(params["mergeSources"])
                    ? params["mergeSources"].join(',')
                    : params["mergeSources"];
                extractedParams.push("mergeSources");
            }
            if (params["syncToken"] !== undefined) {
                queryParams["syncToken"] = params["syncToken"];
                extractedParams.push("syncToken");
            }
            if (params["requestSyncToken"] !== undefined) {
                queryParams["requestSyncToken"] = params["requestSyncToken"];
                extractedParams.push("requestSyncToken");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.readMask) {
                throw new Error(`Missing required parameter: readMask`);
            }
            if (!params.sources) {
                throw new Error(`Missing required parameter: sources`);
            }
            const path = this.buildPath('/v1/people:listDirectoryPeople', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'GET', url: path, params: queryParams });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'list_directory_people',
                method: 'GET',
                path: '/v1/people:listDirectoryPeople',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'list_directory_people',
                method: 'GET',
                path: '/v1/people:listDirectoryPeople',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute list_directory_people: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async searchDirectoryPeople(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'search_directory_people',
            method: 'GET',
            path: '/v1/people:searchDirectoryPeople',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/v1/people:searchDirectoryPeople';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["query"] !== undefined) {
                queryParams["query"] = params["query"];
                extractedParams.push("query");
            }
            if (params["pageSize"] !== undefined) {
                queryParams["pageSize"] = params["pageSize"];
                extractedParams.push("pageSize");
            }
            if (params["pageToken"] !== undefined) {
                queryParams["pageToken"] = params["pageToken"];
                extractedParams.push("pageToken");
            }
            if (params["readMask"] !== undefined) {
                queryParams["readMask"] = params["readMask"];
                extractedParams.push("readMask");
            }
            if (params["sources"] !== undefined) {
                // Handle array parameters - convert to comma-separated string for Google APIs
                queryParams["sources"] = Array.isArray(params["sources"])
                    ? params["sources"].join(',')
                    : params["sources"];
                extractedParams.push("sources");
            }
            if (params["mergeSources"] !== undefined) {
                // Handle array parameters - convert to comma-separated string for Google APIs
                queryParams["mergeSources"] = Array.isArray(params["mergeSources"])
                    ? params["mergeSources"].join(',')
                    : params["mergeSources"];
                extractedParams.push("mergeSources");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.query) {
                throw new Error(`Missing required parameter: query`);
            }
            if (!params.readMask) {
                throw new Error(`Missing required parameter: readMask`);
            }
            if (!params.sources) {
                throw new Error(`Missing required parameter: sources`);
            }
            const path = this.buildPath('/v1/people:searchDirectoryPeople', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'GET', url: path, params: queryParams });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'search_directory_people',
                method: 'GET',
                path: '/v1/people:searchDirectoryPeople',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'search_directory_people',
                method: 'GET',
                path: '/v1/people:searchDirectoryPeople',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute search_directory_people: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async listOtherContacts(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'list_other_contacts',
            method: 'GET',
            path: '/v1/otherContacts',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/v1/otherContacts';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["pageSize"] !== undefined) {
                queryParams["pageSize"] = params["pageSize"];
                extractedParams.push("pageSize");
            }
            if (params["pageToken"] !== undefined) {
                queryParams["pageToken"] = params["pageToken"];
                extractedParams.push("pageToken");
            }
            if (params["readMask"] !== undefined) {
                queryParams["readMask"] = params["readMask"];
                extractedParams.push("readMask");
            }
            if (params["sources"] !== undefined) {
                queryParams["sources"] = params["sources"];
                extractedParams.push("sources");
            }
            if (params["syncToken"] !== undefined) {
                queryParams["syncToken"] = params["syncToken"];
                extractedParams.push("syncToken");
            }
            if (params["requestSyncToken"] !== undefined) {
                queryParams["requestSyncToken"] = params["requestSyncToken"];
                extractedParams.push("requestSyncToken");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.readMask) {
                throw new Error(`Missing required parameter: readMask`);
            }
            const path = this.buildPath('/v1/otherContacts', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'GET', url: path, params: queryParams });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'list_other_contacts',
                method: 'GET',
                path: '/v1/otherContacts',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'list_other_contacts',
                method: 'GET',
                path: '/v1/otherContacts',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute list_other_contacts: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async searchOtherContacts(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'search_other_contacts',
            method: 'GET',
            path: '/v1/otherContacts:search',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/v1/otherContacts:search';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["query"] !== undefined) {
                queryParams["query"] = params["query"];
                extractedParams.push("query");
            }
            if (params["pageSize"] !== undefined) {
                queryParams["pageSize"] = params["pageSize"];
                extractedParams.push("pageSize");
            }
            if (params["readMask"] !== undefined) {
                queryParams["readMask"] = params["readMask"];
                extractedParams.push("readMask");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.query) {
                throw new Error(`Missing required parameter: query`);
            }
            const path = this.buildPath('/v1/otherContacts:search', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'GET', url: path, params: queryParams });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'search_other_contacts',
                method: 'GET',
                path: '/v1/otherContacts:search',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'search_other_contacts',
                method: 'GET',
                path: '/v1/otherContacts:search',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute search_other_contacts: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async copyOtherContact(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'copy_other_contact',
            method: 'POST',
            path: '/v1/{resourceName=otherContacts/*}:copyOtherContactToMyContactsGroup',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/v1/{resourceName=otherContacts/*}:copyOtherContactToMyContactsGroup';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["copyMask"] !== undefined) {
                bodyParams["copyMask"] = params["copyMask"];
                extractedParams.push("copyMask");
            }
            if (params["sources"] !== undefined) {
                bodyParams["sources"] = params["sources"];
                extractedParams.push("sources");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.resourceName) {
                throw new Error(`Missing required parameter: resourceName`);
            }
            if (!params.copyMask) {
                throw new Error(`Missing required parameter: copyMask`);
            }
            const path = this.buildPath('/v1/{resourceName=otherContacts/*}:copyOtherContactToMyContactsGroup', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'POST', url: path, params: queryParams, data: Object.keys(bodyParams).length > 0 ? bodyParams : undefined });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'copy_other_contact',
                method: 'POST',
                path: '/v1/{resourceName=otherContacts/*}:copyOtherContactToMyContactsGroup',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'copy_other_contact',
                method: 'POST',
                path: '/v1/{resourceName=otherContacts/*}:copyOtherContactToMyContactsGroup',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute copy_other_contact: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
}
//# sourceMappingURL=google-contacts-client.js.map