import { gzipSync } from 'zlib' import { createLogger } from '@sim/logger' import type { TinybirdEventsParams, TinybirdEventsResponse } from '@/tools/tinybird/types' import type { ToolConfig } from '@/tools/types' const logger = createLogger('tinybird-events') export const eventsTool: ToolConfig = { id: 'tinybird_events', name: 'Tinybird Events', description: 'Send events to a Tinybird Data Source using the Events API. Supports JSON and NDJSON formats with optional gzip compression.', version: '1.0.0', errorExtractor: 'nested-error-object', params: { base_url: { type: 'string', required: true, visibility: 'user-only', description: 'Tinybird API base URL (e.g., https://api.tinybird.co or https://api.us-east.tinybird.co)', }, datasource: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Name of the Tinybird Data Source to send events to. Example: "events_raw", "user_analytics"', }, data: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Data to send as NDJSON (newline-delimited JSON) or JSON string. Each event should be a valid JSON object. Example NDJSON: {"user_id": 1, "event": "click"}\\n{"user_id": 2, "event": "view"}', }, wait: { type: 'boolean', required: false, visibility: 'user-only', description: 'Wait for database acknowledgment before responding. Enables safer retries but introduces latency. Defaults to false.', }, format: { type: 'string', required: false, visibility: 'user-only', description: 'Format of the events data: "ndjson" (default) or "json"', }, compression: { type: 'string', required: false, visibility: 'user-only', description: 'Compression format: "none" (default) or "gzip"', }, token: { type: 'string', required: true, visibility: 'user-only', description: 'Tinybird API Token with DATASOURCES:APPEND or DATASOURCES:CREATE scope', }, }, request: { url: (params) => { const baseUrl = params.base_url.trim().replace(/\/+$/, '') const url = new URL(`${baseUrl}/v0/events`) url.searchParams.set('name', params.datasource.trim()) // Tinybird selects JSON parsing via the `format=json` query parameter, not the // Content-Type header. Default (omitted) is NDJSON. if (params.format === 'json') { url.searchParams.set('format', 'json') } if (params.wait) { url.searchParams.set('wait', 'true') } return url.toString() }, method: 'POST', headers: (params) => { const headers: Record = { Authorization: `Bearer ${params.token.trim()}`, } if (params.compression === 'gzip') { headers['Content-Encoding'] = 'gzip' } if (params.format === 'json') { headers['Content-Type'] = 'application/json' } else { headers['Content-Type'] = 'application/x-ndjson' } return headers }, body: (params) => { const data = params.data if (params.compression === 'gzip') { return gzipSync(Buffer.from(data, 'utf-8')) } return data }, }, transformResponse: async (response: Response) => { const data = await response.json() logger.info('Successfully sent events to Tinybird', { successful: data.successful_rows, quarantined: data.quarantined_rows, }) return { success: true, output: { successful_rows: data.successful_rows ?? 0, quarantined_rows: data.quarantined_rows ?? 0, }, } }, outputs: { successful_rows: { type: 'number', description: 'Number of rows successfully ingested', }, quarantined_rows: { type: 'number', description: 'Number of rows quarantined (failed validation)', }, }, }