chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
+187
View File
@@ -0,0 +1,187 @@
import type {
ElasticsearchBulkParams,
ElasticsearchBulkResponse,
} from '@/tools/elasticsearch/types'
import type { ToolConfig } from '@/tools/types'
function buildBaseUrl(params: ElasticsearchBulkParams): string {
if (params.deploymentType === 'cloud' && params.cloudId) {
const parts = params.cloudId.split(':')
if (parts.length >= 2) {
try {
const decoded = Buffer.from(parts[1], 'base64').toString('utf-8')
const [esHost] = decoded.split('$')
if (esHost) {
return `https://${parts[0]}.${esHost}`
}
} catch {
// Fallback
}
}
throw new Error('Invalid Cloud ID format')
}
if (!params.host) {
throw new Error('Host is required for self-hosted deployments')
}
return params.host.replace(/\/$/, '')
}
function buildAuthHeaders(params: ElasticsearchBulkParams): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/x-ndjson',
}
if (params.authMethod === 'api_key' && params.apiKey) {
headers.Authorization = `ApiKey ${params.apiKey}`
} else if (params.authMethod === 'basic_auth' && params.username && params.password) {
const credentials = Buffer.from(`${params.username}:${params.password}`).toString('base64')
headers.Authorization = `Basic ${credentials}`
} else {
throw new Error('Invalid authentication configuration')
}
return headers
}
export const bulkTool: ToolConfig<ElasticsearchBulkParams, ElasticsearchBulkResponse> = {
id: 'elasticsearch_bulk',
name: 'Elasticsearch Bulk Operations',
description:
'Perform multiple index, create, delete, or update operations in a single request for high performance.',
version: '1.0.0',
params: {
deploymentType: {
type: 'string',
required: true,
description: 'Deployment type: self_hosted or cloud',
},
host: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch host URL (for self-hosted)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elastic Cloud ID (for cloud deployments)',
},
authMethod: {
type: 'string',
required: true,
description: 'Authentication method: api_key or basic_auth',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch API key',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username for basic auth',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password for basic auth',
},
index: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Default index for operations (e.g., "products", "logs-2024")',
},
operations: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Bulk operations as NDJSON string. Each operation is two lines: action metadata and optional document. Example: {"index":{"_index":"products","_id":"1"}}\\n{"name":"Widget"}\\n',
},
refresh: {
type: 'string',
required: false,
description: 'Refresh policy: true, false, or wait_for',
},
},
request: {
url: (params) => {
const baseUrl = buildBaseUrl(params)
let url = params.index
? `${baseUrl}/${encodeURIComponent(params.index)}/_bulk`
: `${baseUrl}/_bulk`
if (params.refresh) {
url += `?refresh=${params.refresh}`
}
return url
},
method: 'POST',
headers: (params) => buildAuthHeaders(params),
body: (params) => {
// The body should be NDJSON format - we pass it as raw string
// Ensure it ends with a newline
// Note: The executor in tools/utils.ts handles NDJSON content-type specially
// and accepts string bodies directly
let operations = params.operations.trim()
if (!operations.endsWith('\n')) {
operations += '\n'
}
return operations
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
let errorMessage = `Elasticsearch error: ${response.status}`
try {
const errorJson = JSON.parse(errorText)
errorMessage = errorJson.error?.reason || errorJson.error?.type || errorMessage
} catch {
errorMessage = errorText || errorMessage
}
return {
success: false,
output: { took: 0, errors: true, items: [] },
error: errorMessage,
}
}
const data = await response.json()
return {
success: true,
output: {
took: data.took,
errors: data.errors,
items: data.items,
},
}
},
outputs: {
took: {
type: 'number',
description: 'Time in milliseconds the bulk operation took',
},
errors: {
type: 'boolean',
description: 'Whether any operation had an error',
},
items: {
type: 'array',
description: 'Results for each operation',
},
},
}
@@ -0,0 +1,215 @@
import type {
ElasticsearchClusterHealthParams,
ElasticsearchClusterHealthResponse,
} from '@/tools/elasticsearch/types'
import type { ToolConfig } from '@/tools/types'
function buildBaseUrl(params: ElasticsearchClusterHealthParams): string {
if (params.deploymentType === 'cloud' && params.cloudId) {
const parts = params.cloudId.split(':')
if (parts.length >= 2) {
try {
const decoded = Buffer.from(parts[1], 'base64').toString('utf-8')
const [esHost] = decoded.split('$')
if (esHost) {
return `https://${parts[0]}.${esHost}`
}
} catch {
// Fallback
}
}
throw new Error('Invalid Cloud ID format')
}
if (!params.host) {
throw new Error('Host is required for self-hosted deployments')
}
return params.host.replace(/\/$/, '')
}
function buildAuthHeaders(params: ElasticsearchClusterHealthParams): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (params.authMethod === 'api_key' && params.apiKey) {
headers.Authorization = `ApiKey ${params.apiKey}`
} else if (params.authMethod === 'basic_auth' && params.username && params.password) {
const credentials = Buffer.from(`${params.username}:${params.password}`).toString('base64')
headers.Authorization = `Basic ${credentials}`
} else {
throw new Error('Invalid authentication configuration')
}
return headers
}
export const clusterHealthTool: ToolConfig<
ElasticsearchClusterHealthParams,
ElasticsearchClusterHealthResponse
> = {
id: 'elasticsearch_cluster_health',
name: 'Elasticsearch Cluster Health',
description: 'Get the health status of the Elasticsearch cluster.',
version: '1.0.0',
params: {
deploymentType: {
type: 'string',
required: true,
description: 'Deployment type: self_hosted or cloud',
},
host: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch host URL (for self-hosted)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elastic Cloud ID (for cloud deployments)',
},
authMethod: {
type: 'string',
required: true,
description: 'Authentication method: api_key or basic_auth',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch API key',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username for basic auth',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password for basic auth',
},
waitForStatus: {
type: 'string',
required: false,
description: 'Wait until cluster reaches this status: green, yellow, or red',
},
timeout: {
type: 'string',
required: false,
description: 'Timeout for the wait operation (e.g., 30s, 1m)',
},
},
request: {
url: (params) => {
const baseUrl = buildBaseUrl(params)
let url = `${baseUrl}/_cluster/health`
const queryParams: string[] = []
if (params.waitForStatus) {
queryParams.push(`wait_for_status=${params.waitForStatus}`)
}
if (params.timeout) {
queryParams.push(`timeout=${encodeURIComponent(params.timeout)}`)
}
if (queryParams.length > 0) {
url += `?${queryParams.join('&')}`
}
return url
},
method: 'GET',
headers: (params) => buildAuthHeaders(params),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
let errorMessage = `Elasticsearch error: ${response.status}`
try {
const errorJson = JSON.parse(errorText)
errorMessage = errorJson.error?.reason || errorJson.error?.type || errorMessage
} catch {
errorMessage = errorText || errorMessage
}
return {
success: false,
output: {
cluster_name: '',
status: 'red' as const,
timed_out: true,
number_of_nodes: 0,
number_of_data_nodes: 0,
active_primary_shards: 0,
active_shards: 0,
relocating_shards: 0,
initializing_shards: 0,
unassigned_shards: 0,
delayed_unassigned_shards: 0,
number_of_pending_tasks: 0,
number_of_in_flight_fetch: 0,
task_max_waiting_in_queue_millis: 0,
active_shards_percent_as_number: 0,
},
error: errorMessage,
}
}
const data = await response.json()
return {
success: true,
output: {
cluster_name: data.cluster_name,
status: data.status,
timed_out: data.timed_out,
number_of_nodes: data.number_of_nodes,
number_of_data_nodes: data.number_of_data_nodes,
active_primary_shards: data.active_primary_shards,
active_shards: data.active_shards,
relocating_shards: data.relocating_shards,
initializing_shards: data.initializing_shards,
unassigned_shards: data.unassigned_shards,
delayed_unassigned_shards: data.delayed_unassigned_shards,
number_of_pending_tasks: data.number_of_pending_tasks,
number_of_in_flight_fetch: data.number_of_in_flight_fetch,
task_max_waiting_in_queue_millis: data.task_max_waiting_in_queue_millis,
active_shards_percent_as_number: data.active_shards_percent_as_number,
},
}
},
outputs: {
cluster_name: {
type: 'string',
description: 'Name of the cluster',
},
status: {
type: 'string',
description: 'Cluster health status: green, yellow, or red',
},
number_of_nodes: {
type: 'number',
description: 'Total number of nodes in the cluster',
},
number_of_data_nodes: {
type: 'number',
description: 'Number of data nodes',
},
active_shards: {
type: 'number',
description: 'Number of active shards',
},
unassigned_shards: {
type: 'number',
description: 'Number of unassigned shards',
},
},
}
@@ -0,0 +1,180 @@
import type {
ElasticsearchClusterStatsParams,
ElasticsearchClusterStatsResponse,
} from '@/tools/elasticsearch/types'
import type { ToolConfig } from '@/tools/types'
function buildBaseUrl(params: ElasticsearchClusterStatsParams): string {
if (params.deploymentType === 'cloud' && params.cloudId) {
const parts = params.cloudId.split(':')
if (parts.length >= 2) {
try {
const decoded = Buffer.from(parts[1], 'base64').toString('utf-8')
const [esHost] = decoded.split('$')
if (esHost) {
return `https://${parts[0]}.${esHost}`
}
} catch {
// Fallback
}
}
throw new Error('Invalid Cloud ID format')
}
if (!params.host) {
throw new Error('Host is required for self-hosted deployments')
}
return params.host.replace(/\/$/, '')
}
function buildAuthHeaders(params: ElasticsearchClusterStatsParams): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (params.authMethod === 'api_key' && params.apiKey) {
headers.Authorization = `ApiKey ${params.apiKey}`
} else if (params.authMethod === 'basic_auth' && params.username && params.password) {
const credentials = Buffer.from(`${params.username}:${params.password}`).toString('base64')
headers.Authorization = `Basic ${credentials}`
} else {
throw new Error('Invalid authentication configuration')
}
return headers
}
export const clusterStatsTool: ToolConfig<
ElasticsearchClusterStatsParams,
ElasticsearchClusterStatsResponse
> = {
id: 'elasticsearch_cluster_stats',
name: 'Elasticsearch Cluster Stats',
description: 'Get comprehensive statistics about the Elasticsearch cluster.',
version: '1.0.0',
params: {
deploymentType: {
type: 'string',
required: true,
description: 'Deployment type: self_hosted or cloud',
},
host: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch host URL (for self-hosted)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elastic Cloud ID (for cloud deployments)',
},
authMethod: {
type: 'string',
required: true,
description: 'Authentication method: api_key or basic_auth',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch API key',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username for basic auth',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password for basic auth',
},
},
request: {
url: (params) => {
const baseUrl = buildBaseUrl(params)
return `${baseUrl}/_cluster/stats`
},
method: 'GET',
headers: (params) => buildAuthHeaders(params),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
let errorMessage = `Elasticsearch error: ${response.status}`
try {
const errorJson = JSON.parse(errorText)
errorMessage = errorJson.error?.reason || errorJson.error?.type || errorMessage
} catch {
errorMessage = errorText || errorMessage
}
return {
success: false,
output: {
cluster_name: '',
cluster_uuid: '',
status: 'red',
nodes: {
count: { total: 0, data: 0, master: 0 },
versions: [],
},
indices: {
count: 0,
docs: { count: 0, deleted: 0 },
store: { size_in_bytes: 0 },
shards: { total: 0, primaries: 0 },
},
},
error: errorMessage,
}
}
const data = await response.json()
return {
success: true,
output: {
cluster_name: data.cluster_name,
cluster_uuid: data.cluster_uuid,
status: data.status,
nodes: {
count: data.nodes?.count || { total: 0, data: 0, master: 0 },
versions: data.nodes?.versions || [],
},
indices: {
count: data.indices?.count || 0,
docs: data.indices?.docs || { count: 0, deleted: 0 },
store: data.indices?.store || { size_in_bytes: 0 },
shards: data.indices?.shards || { total: 0, primaries: 0 },
},
},
}
},
outputs: {
cluster_name: {
type: 'string',
description: 'Name of the cluster',
},
status: {
type: 'string',
description: 'Cluster health status',
},
nodes: {
type: 'object',
description: 'Node statistics including count and versions',
},
indices: {
type: 'object',
description: 'Index statistics including document count and store size',
},
},
}
+170
View File
@@ -0,0 +1,170 @@
import type {
ElasticsearchCountParams,
ElasticsearchCountResponse,
} from '@/tools/elasticsearch/types'
import type { ToolConfig } from '@/tools/types'
function buildBaseUrl(params: ElasticsearchCountParams): string {
if (params.deploymentType === 'cloud' && params.cloudId) {
const parts = params.cloudId.split(':')
if (parts.length >= 2) {
try {
const decoded = Buffer.from(parts[1], 'base64').toString('utf-8')
const [esHost] = decoded.split('$')
if (esHost) {
return `https://${parts[0]}.${esHost}`
}
} catch {
// Fallback
}
}
throw new Error('Invalid Cloud ID format')
}
if (!params.host) {
throw new Error('Host is required for self-hosted deployments')
}
return params.host.replace(/\/$/, '')
}
function buildAuthHeaders(params: ElasticsearchCountParams): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (params.authMethod === 'api_key' && params.apiKey) {
headers.Authorization = `ApiKey ${params.apiKey}`
} else if (params.authMethod === 'basic_auth' && params.username && params.password) {
const credentials = Buffer.from(`${params.username}:${params.password}`).toString('base64')
headers.Authorization = `Basic ${credentials}`
} else {
throw new Error('Invalid authentication configuration')
}
return headers
}
export const countTool: ToolConfig<ElasticsearchCountParams, ElasticsearchCountResponse> = {
id: 'elasticsearch_count',
name: 'Elasticsearch Count',
description: 'Count documents matching a query in Elasticsearch.',
version: '1.0.0',
params: {
deploymentType: {
type: 'string',
required: true,
description: 'Deployment type: self_hosted or cloud',
},
host: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch host URL (for self-hosted)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elastic Cloud ID (for cloud deployments)',
},
authMethod: {
type: 'string',
required: true,
description: 'Authentication method: api_key or basic_auth',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch API key',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username for basic auth',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password for basic auth',
},
index: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Index name to count documents in (e.g., "products", "logs-2024")',
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Query DSL to filter documents (JSON string). Example: {"match":{"status":"active"}}',
},
},
request: {
url: (params) => {
const baseUrl = buildBaseUrl(params)
return `${baseUrl}/${encodeURIComponent(params.index)}/_count`
},
method: 'POST',
headers: (params) => buildAuthHeaders(params),
body: (params) => {
if (params.query) {
try {
return { query: JSON.parse(params.query) }
} catch {
return {}
}
}
return {}
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
let errorMessage = `Elasticsearch error: ${response.status}`
try {
const errorJson = JSON.parse(errorText)
errorMessage = errorJson.error?.reason || errorJson.error?.type || errorMessage
} catch {
errorMessage = errorText || errorMessage
}
return {
success: false,
output: {
count: 0,
_shards: { total: 0, successful: 0, skipped: 0, failed: 0 },
},
error: errorMessage,
}
}
const data = await response.json()
return {
success: true,
output: {
count: data.count,
_shards: data._shards,
},
}
},
outputs: {
count: {
type: 'number',
description: 'Number of documents matching the query',
},
_shards: {
type: 'object',
description: 'Shard statistics',
},
},
}
@@ -0,0 +1,189 @@
import type {
ElasticsearchCreateIndexParams,
ElasticsearchIndexResponse,
} from '@/tools/elasticsearch/types'
import type { ToolConfig } from '@/tools/types'
function buildBaseUrl(params: ElasticsearchCreateIndexParams): string {
if (params.deploymentType === 'cloud' && params.cloudId) {
const parts = params.cloudId.split(':')
if (parts.length >= 2) {
try {
const decoded = Buffer.from(parts[1], 'base64').toString('utf-8')
const [esHost] = decoded.split('$')
if (esHost) {
return `https://${parts[0]}.${esHost}`
}
} catch {
// Fallback
}
}
throw new Error('Invalid Cloud ID format')
}
if (!params.host) {
throw new Error('Host is required for self-hosted deployments')
}
return params.host.replace(/\/$/, '')
}
function buildAuthHeaders(params: ElasticsearchCreateIndexParams): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (params.authMethod === 'api_key' && params.apiKey) {
headers.Authorization = `ApiKey ${params.apiKey}`
} else if (params.authMethod === 'basic_auth' && params.username && params.password) {
const credentials = Buffer.from(`${params.username}:${params.password}`).toString('base64')
headers.Authorization = `Basic ${credentials}`
} else {
throw new Error('Invalid authentication configuration')
}
return headers
}
export const createIndexTool: ToolConfig<
ElasticsearchCreateIndexParams,
ElasticsearchIndexResponse
> = {
id: 'elasticsearch_create_index',
name: 'Elasticsearch Create Index',
description: 'Create a new index with optional settings and mappings.',
version: '1.0.0',
params: {
deploymentType: {
type: 'string',
required: true,
description: 'Deployment type: self_hosted or cloud',
},
host: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch host URL (for self-hosted)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elastic Cloud ID (for cloud deployments)',
},
authMethod: {
type: 'string',
required: true,
description: 'Authentication method: api_key or basic_auth',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch API key',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username for basic auth',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password for basic auth',
},
index: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Index name to create (e.g., "products", "logs-2024")',
},
settings: {
type: 'string',
required: false,
description: 'Index settings as JSON string',
},
mappings: {
type: 'string',
required: false,
description: 'Index mappings as JSON string',
},
},
request: {
url: (params) => {
const baseUrl = buildBaseUrl(params)
return `${baseUrl}/${encodeURIComponent(params.index)}`
},
method: 'PUT',
headers: (params) => buildAuthHeaders(params),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.settings) {
try {
body.settings = JSON.parse(params.settings)
} catch {
// Ignore invalid settings
}
}
if (params.mappings) {
try {
body.mappings = JSON.parse(params.mappings)
} catch {
// Ignore invalid mappings
}
}
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
let errorMessage = `Elasticsearch error: ${response.status}`
try {
const errorJson = JSON.parse(errorText)
errorMessage = errorJson.error?.reason || errorJson.error?.type || errorMessage
} catch {
errorMessage = errorText || errorMessage
}
return {
success: false,
output: { acknowledged: false },
error: errorMessage,
}
}
const data = await response.json()
return {
success: true,
output: {
acknowledged: data.acknowledged,
shards_acknowledged: data.shards_acknowledged,
index: data.index,
},
}
},
outputs: {
acknowledged: {
type: 'boolean',
description: 'Whether the request was acknowledged',
},
shards_acknowledged: {
type: 'boolean',
description: 'Whether the shards were acknowledged',
},
index: {
type: 'string',
description: 'Created index name',
},
},
}
@@ -0,0 +1,191 @@
import type {
ElasticsearchDeleteDocumentParams,
ElasticsearchDocumentResponse,
} from '@/tools/elasticsearch/types'
import type { ToolConfig } from '@/tools/types'
function buildBaseUrl(params: ElasticsearchDeleteDocumentParams): string {
if (params.deploymentType === 'cloud' && params.cloudId) {
const parts = params.cloudId.split(':')
if (parts.length >= 2) {
try {
const decoded = Buffer.from(parts[1], 'base64').toString('utf-8')
const [esHost] = decoded.split('$')
if (esHost) {
return `https://${parts[0]}.${esHost}`
}
} catch {
// Fallback
}
}
throw new Error('Invalid Cloud ID format')
}
if (!params.host) {
throw new Error('Host is required for self-hosted deployments')
}
return params.host.replace(/\/$/, '')
}
function buildAuthHeaders(params: ElasticsearchDeleteDocumentParams): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (params.authMethod === 'api_key' && params.apiKey) {
headers.Authorization = `ApiKey ${params.apiKey}`
} else if (params.authMethod === 'basic_auth' && params.username && params.password) {
const credentials = Buffer.from(`${params.username}:${params.password}`).toString('base64')
headers.Authorization = `Basic ${credentials}`
} else {
throw new Error('Invalid authentication configuration')
}
return headers
}
export const deleteDocumentTool: ToolConfig<
ElasticsearchDeleteDocumentParams,
ElasticsearchDocumentResponse
> = {
id: 'elasticsearch_delete_document',
name: 'Elasticsearch Delete Document',
description: 'Delete a document from Elasticsearch by ID.',
version: '1.0.0',
params: {
deploymentType: {
type: 'string',
required: true,
description: 'Deployment type: self_hosted or cloud',
},
host: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch host URL (for self-hosted)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elastic Cloud ID (for cloud deployments)',
},
authMethod: {
type: 'string',
required: true,
description: 'Authentication method: api_key or basic_auth',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch API key',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username for basic auth',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password for basic auth',
},
index: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Index name (e.g., "products", "logs-2024")',
},
documentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Document ID to delete (e.g., "abc123", "user_456")',
},
refresh: {
type: 'string',
required: false,
description: 'Refresh policy: true, false, or wait_for',
},
},
request: {
url: (params) => {
const baseUrl = buildBaseUrl(params)
let url = `${baseUrl}/${encodeURIComponent(params.index)}/_doc/${encodeURIComponent(params.documentId)}`
if (params.refresh) {
url += `?refresh=${params.refresh}`
}
return url
},
method: 'DELETE',
headers: (params) => buildAuthHeaders(params),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
if (response.status === 404) {
return {
success: true,
output: {
_index: '',
_id: '',
result: 'not_found',
},
}
}
const errorText = await response.text()
let errorMessage = `Elasticsearch error: ${response.status}`
try {
const errorJson = JSON.parse(errorText)
errorMessage = errorJson.error?.reason || errorJson.error?.type || errorMessage
} catch {
errorMessage = errorText || errorMessage
}
return {
success: false,
output: { _index: '', _id: '' },
error: errorMessage,
}
}
const data = await response.json()
return {
success: true,
output: {
_index: data._index,
_id: data._id,
_version: data._version,
result: data.result,
},
}
},
outputs: {
_index: {
type: 'string',
description: 'Index name',
},
_id: {
type: 'string',
description: 'Document ID',
},
_version: {
type: 'number',
description: 'Document version',
},
result: {
type: 'string',
description: 'Operation result (deleted or not_found)',
},
},
}
@@ -0,0 +1,148 @@
import type {
ElasticsearchDeleteIndexParams,
ElasticsearchIndexResponse,
} from '@/tools/elasticsearch/types'
import type { ToolConfig } from '@/tools/types'
function buildBaseUrl(params: ElasticsearchDeleteIndexParams): string {
if (params.deploymentType === 'cloud' && params.cloudId) {
const parts = params.cloudId.split(':')
if (parts.length >= 2) {
try {
const decoded = Buffer.from(parts[1], 'base64').toString('utf-8')
const [esHost] = decoded.split('$')
if (esHost) {
return `https://${parts[0]}.${esHost}`
}
} catch {
// Fallback
}
}
throw new Error('Invalid Cloud ID format')
}
if (!params.host) {
throw new Error('Host is required for self-hosted deployments')
}
return params.host.replace(/\/$/, '')
}
function buildAuthHeaders(params: ElasticsearchDeleteIndexParams): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (params.authMethod === 'api_key' && params.apiKey) {
headers.Authorization = `ApiKey ${params.apiKey}`
} else if (params.authMethod === 'basic_auth' && params.username && params.password) {
const credentials = Buffer.from(`${params.username}:${params.password}`).toString('base64')
headers.Authorization = `Basic ${credentials}`
} else {
throw new Error('Invalid authentication configuration')
}
return headers
}
export const deleteIndexTool: ToolConfig<
ElasticsearchDeleteIndexParams,
ElasticsearchIndexResponse
> = {
id: 'elasticsearch_delete_index',
name: 'Elasticsearch Delete Index',
description: 'Delete an index and all its documents. This operation is irreversible.',
version: '1.0.0',
params: {
deploymentType: {
type: 'string',
required: true,
description: 'Deployment type: self_hosted or cloud',
},
host: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch host URL (for self-hosted)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elastic Cloud ID (for cloud deployments)',
},
authMethod: {
type: 'string',
required: true,
description: 'Authentication method: api_key or basic_auth',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch API key',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username for basic auth',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password for basic auth',
},
index: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Index name to delete (e.g., "products", "logs-2024")',
},
},
request: {
url: (params) => {
const baseUrl = buildBaseUrl(params)
return `${baseUrl}/${encodeURIComponent(params.index)}`
},
method: 'DELETE',
headers: (params) => buildAuthHeaders(params),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
let errorMessage = `Elasticsearch error: ${response.status}`
try {
const errorJson = JSON.parse(errorText)
errorMessage = errorJson.error?.reason || errorJson.error?.type || errorMessage
} catch {
errorMessage = errorText || errorMessage
}
return {
success: false,
output: { acknowledged: false },
error: errorMessage,
}
}
const data = await response.json()
return {
success: true,
output: {
acknowledged: data.acknowledged,
},
}
},
outputs: {
acknowledged: {
type: 'boolean',
description: 'Whether the deletion was acknowledged',
},
},
}
@@ -0,0 +1,208 @@
import type {
ElasticsearchDocumentResponse,
ElasticsearchGetDocumentParams,
} from '@/tools/elasticsearch/types'
import type { ToolConfig } from '@/tools/types'
function buildBaseUrl(params: ElasticsearchGetDocumentParams): string {
if (params.deploymentType === 'cloud' && params.cloudId) {
const parts = params.cloudId.split(':')
if (parts.length >= 2) {
try {
const decoded = Buffer.from(parts[1], 'base64').toString('utf-8')
const [esHost] = decoded.split('$')
if (esHost) {
return `https://${parts[0]}.${esHost}`
}
} catch {
// Fallback
}
}
throw new Error('Invalid Cloud ID format')
}
if (!params.host) {
throw new Error('Host is required for self-hosted deployments')
}
return params.host.replace(/\/$/, '')
}
function buildAuthHeaders(params: ElasticsearchGetDocumentParams): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (params.authMethod === 'api_key' && params.apiKey) {
headers.Authorization = `ApiKey ${params.apiKey}`
} else if (params.authMethod === 'basic_auth' && params.username && params.password) {
const credentials = Buffer.from(`${params.username}:${params.password}`).toString('base64')
headers.Authorization = `Basic ${credentials}`
} else {
throw new Error('Invalid authentication configuration')
}
return headers
}
export const getDocumentTool: ToolConfig<
ElasticsearchGetDocumentParams,
ElasticsearchDocumentResponse
> = {
id: 'elasticsearch_get_document',
name: 'Elasticsearch Get Document',
description: 'Retrieve a document by ID from Elasticsearch.',
version: '1.0.0',
params: {
deploymentType: {
type: 'string',
required: true,
description: 'Deployment type: self_hosted or cloud',
},
host: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch host URL (for self-hosted)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elastic Cloud ID (for cloud deployments)',
},
authMethod: {
type: 'string',
required: true,
description: 'Authentication method: api_key or basic_auth',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch API key',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username for basic auth',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password for basic auth',
},
index: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Index name (e.g., "products", "logs-2024")',
},
documentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Document ID to retrieve (e.g., "abc123", "user_456")',
},
sourceIncludes: {
type: 'string',
required: false,
description: 'Comma-separated list of fields to include',
},
sourceExcludes: {
type: 'string',
required: false,
description: 'Comma-separated list of fields to exclude',
},
},
request: {
url: (params) => {
const baseUrl = buildBaseUrl(params)
let url = `${baseUrl}/${encodeURIComponent(params.index)}/_doc/${encodeURIComponent(params.documentId)}`
const queryParams: string[] = []
if (params.sourceIncludes) {
queryParams.push(`_source_includes=${encodeURIComponent(params.sourceIncludes)}`)
}
if (params.sourceExcludes) {
queryParams.push(`_source_excludes=${encodeURIComponent(params.sourceExcludes)}`)
}
if (queryParams.length > 0) {
url += `?${queryParams.join('&')}`
}
return url
},
method: 'GET',
headers: (params) => buildAuthHeaders(params),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
if (response.status === 404) {
return {
success: true,
output: {
_index: '',
_id: '',
found: false,
},
}
}
const errorText = await response.text()
let errorMessage = `Elasticsearch error: ${response.status}`
try {
const errorJson = JSON.parse(errorText)
errorMessage = errorJson.error?.reason || errorJson.error?.type || errorMessage
} catch {
errorMessage = errorText || errorMessage
}
return {
success: false,
output: { _index: '', _id: '' },
error: errorMessage,
}
}
const data = await response.json()
return {
success: true,
output: {
_index: data._index,
_id: data._id,
_version: data._version,
found: data.found,
_source: data._source,
},
}
},
outputs: {
_index: {
type: 'string',
description: 'Index name',
},
_id: {
type: 'string',
description: 'Document ID',
},
_version: {
type: 'number',
description: 'Document version',
},
found: {
type: 'boolean',
description: 'Whether the document was found',
},
_source: {
type: 'json',
description: 'Document content',
},
},
}
+144
View File
@@ -0,0 +1,144 @@
import type {
ElasticsearchGetIndexParams,
ElasticsearchIndexInfoResponse,
} from '@/tools/elasticsearch/types'
import type { ToolConfig } from '@/tools/types'
function buildBaseUrl(params: ElasticsearchGetIndexParams): string {
if (params.deploymentType === 'cloud' && params.cloudId) {
const parts = params.cloudId.split(':')
if (parts.length >= 2) {
try {
const decoded = Buffer.from(parts[1], 'base64').toString('utf-8')
const [esHost] = decoded.split('$')
if (esHost) {
return `https://${parts[0]}.${esHost}`
}
} catch {
// Fallback
}
}
throw new Error('Invalid Cloud ID format')
}
if (!params.host) {
throw new Error('Host is required for self-hosted deployments')
}
return params.host.replace(/\/$/, '')
}
function buildAuthHeaders(params: ElasticsearchGetIndexParams): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (params.authMethod === 'api_key' && params.apiKey) {
headers.Authorization = `ApiKey ${params.apiKey}`
} else if (params.authMethod === 'basic_auth' && params.username && params.password) {
const credentials = Buffer.from(`${params.username}:${params.password}`).toString('base64')
headers.Authorization = `Basic ${credentials}`
} else {
throw new Error('Invalid authentication configuration')
}
return headers
}
export const getIndexTool: ToolConfig<ElasticsearchGetIndexParams, ElasticsearchIndexInfoResponse> =
{
id: 'elasticsearch_get_index',
name: 'Elasticsearch Get Index',
description: 'Retrieve index information including settings, mappings, and aliases.',
version: '1.0.0',
params: {
deploymentType: {
type: 'string',
required: true,
description: 'Deployment type: self_hosted or cloud',
},
host: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch host URL (for self-hosted)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elastic Cloud ID (for cloud deployments)',
},
authMethod: {
type: 'string',
required: true,
description: 'Authentication method: api_key or basic_auth',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch API key',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username for basic auth',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password for basic auth',
},
index: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Index name to retrieve info for (e.g., "products", "logs-2024")',
},
},
request: {
url: (params) => {
const baseUrl = buildBaseUrl(params)
return `${baseUrl}/${encodeURIComponent(params.index)}`
},
method: 'GET',
headers: (params) => buildAuthHeaders(params),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
let errorMessage = `Elasticsearch error: ${response.status}`
try {
const errorJson = JSON.parse(errorText)
errorMessage = errorJson.error?.reason || errorJson.error?.type || errorMessage
} catch {
errorMessage = errorText || errorMessage
}
return {
success: false,
output: {},
error: errorMessage,
}
}
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
index: {
type: 'json',
description: 'Index information including aliases, mappings, and settings',
},
},
}
+29
View File
@@ -0,0 +1,29 @@
// Elasticsearch tools exports
import { bulkTool } from '@/tools/elasticsearch/bulk'
import { clusterHealthTool } from '@/tools/elasticsearch/cluster_health'
import { clusterStatsTool } from '@/tools/elasticsearch/cluster_stats'
import { countTool } from '@/tools/elasticsearch/count'
import { createIndexTool } from '@/tools/elasticsearch/create_index'
import { deleteDocumentTool } from '@/tools/elasticsearch/delete_document'
import { deleteIndexTool } from '@/tools/elasticsearch/delete_index'
import { getDocumentTool } from '@/tools/elasticsearch/get_document'
import { getIndexTool } from '@/tools/elasticsearch/get_index'
import { indexDocumentTool } from '@/tools/elasticsearch/index_document'
import { listIndicesTool } from '@/tools/elasticsearch/list_indices'
import { searchTool } from '@/tools/elasticsearch/search'
import { updateDocumentTool } from '@/tools/elasticsearch/update_document'
// Export individual tools with elasticsearch prefix
export const elasticsearchSearchTool = searchTool
export const elasticsearchIndexDocumentTool = indexDocumentTool
export const elasticsearchGetDocumentTool = getDocumentTool
export const elasticsearchUpdateDocumentTool = updateDocumentTool
export const elasticsearchDeleteDocumentTool = deleteDocumentTool
export const elasticsearchBulkTool = bulkTool
export const elasticsearchCountTool = countTool
export const elasticsearchCreateIndexTool = createIndexTool
export const elasticsearchDeleteIndexTool = deleteIndexTool
export const elasticsearchGetIndexTool = getIndexTool
export const elasticsearchListIndicesTool = listIndicesTool
export const elasticsearchClusterHealthTool = clusterHealthTool
export const elasticsearchClusterStatsTool = clusterStatsTool
@@ -0,0 +1,193 @@
import type {
ElasticsearchDocumentResponse,
ElasticsearchIndexDocumentParams,
} from '@/tools/elasticsearch/types'
import type { ToolConfig } from '@/tools/types'
function buildBaseUrl(params: ElasticsearchIndexDocumentParams): string {
if (params.deploymentType === 'cloud' && params.cloudId) {
const parts = params.cloudId.split(':')
if (parts.length >= 2) {
try {
const decoded = Buffer.from(parts[1], 'base64').toString('utf-8')
const [esHost] = decoded.split('$')
if (esHost) {
return `https://${parts[0]}.${esHost}`
}
} catch {
// Fallback
}
}
throw new Error('Invalid Cloud ID format')
}
if (!params.host) {
throw new Error('Host is required for self-hosted deployments')
}
return params.host.replace(/\/$/, '')
}
function buildAuthHeaders(params: ElasticsearchIndexDocumentParams): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (params.authMethod === 'api_key' && params.apiKey) {
headers.Authorization = `ApiKey ${params.apiKey}`
} else if (params.authMethod === 'basic_auth' && params.username && params.password) {
const credentials = Buffer.from(`${params.username}:${params.password}`).toString('base64')
headers.Authorization = `Basic ${credentials}`
} else {
throw new Error('Invalid authentication configuration')
}
return headers
}
export const indexDocumentTool: ToolConfig<
ElasticsearchIndexDocumentParams,
ElasticsearchDocumentResponse
> = {
id: 'elasticsearch_index_document',
name: 'Elasticsearch Index Document',
description: 'Index (create or update) a document in Elasticsearch.',
version: '1.0.0',
params: {
deploymentType: {
type: 'string',
required: true,
description: 'Deployment type: self_hosted or cloud',
},
host: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch host URL (for self-hosted)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elastic Cloud ID (for cloud deployments)',
},
authMethod: {
type: 'string',
required: true,
description: 'Authentication method: api_key or basic_auth',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch API key',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username for basic auth',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password for basic auth',
},
index: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Target index name (e.g., "products", "logs-2024")',
},
documentId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Document ID (e.g., "abc123", "user_456"). Auto-generated if not provided',
},
document: {
type: 'string',
required: true,
description: 'Document body as JSON string',
},
refresh: {
type: 'string',
required: false,
description: 'Refresh policy: true, false, or wait_for',
},
},
request: {
url: (params) => {
const baseUrl = buildBaseUrl(params)
let url = `${baseUrl}/${encodeURIComponent(params.index)}/_doc`
if (params.documentId) {
url += `/${encodeURIComponent(params.documentId)}`
}
if (params.refresh) {
url += `?refresh=${params.refresh}`
}
return url
},
method: (params) => (params.documentId ? 'PUT' : 'POST'),
headers: (params) => buildAuthHeaders(params),
body: (params) => {
try {
return JSON.parse(params.document)
} catch {
throw new Error('Invalid JSON document')
}
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
let errorMessage = `Elasticsearch error: ${response.status}`
try {
const errorJson = JSON.parse(errorText)
errorMessage = errorJson.error?.reason || errorJson.error?.type || errorMessage
} catch {
errorMessage = errorText || errorMessage
}
return {
success: false,
output: { _index: '', _id: '' },
error: errorMessage,
}
}
const data = await response.json()
return {
success: true,
output: {
_index: data._index,
_id: data._id,
_version: data._version,
result: data.result,
},
}
},
outputs: {
_index: {
type: 'string',
description: 'Index where the document was stored',
},
_id: {
type: 'string',
description: 'Document ID',
},
_version: {
type: 'number',
description: 'Document version',
},
result: {
type: 'string',
description: 'Operation result (created or updated)',
},
},
}
@@ -0,0 +1,174 @@
import type {
ElasticsearchListIndicesParams,
ElasticsearchListIndicesResponse,
} from '@/tools/elasticsearch/types'
import type { ToolConfig } from '@/tools/types'
/**
* Builds the base URL for Elasticsearch connections.
* Supports both self-hosted and Elastic Cloud deployments.
*/
function buildBaseUrl(params: ElasticsearchListIndicesParams): string {
if (params.deploymentType === 'cloud' && params.cloudId) {
const parts = params.cloudId.split(':')
if (parts.length >= 2) {
try {
const decoded = Buffer.from(parts[1], 'base64').toString('utf-8')
const [esHost] = decoded.split('$')
if (esHost) {
return `https://${parts[0]}.${esHost}`
}
} catch {
// Fallback
}
}
throw new Error('Invalid Cloud ID format')
}
if (!params.host) {
throw new Error('Host is required for self-hosted deployments')
}
return params.host.replace(/\/$/, '')
}
/**
* Builds authentication headers for Elasticsearch requests.
* Supports API key and basic authentication methods.
*/
function buildAuthHeaders(params: ElasticsearchListIndicesParams): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (params.authMethod === 'api_key' && params.apiKey) {
headers.Authorization = `ApiKey ${params.apiKey}`
} else if (params.authMethod === 'basic_auth' && params.username && params.password) {
const credentials = Buffer.from(`${params.username}:${params.password}`).toString('base64')
headers.Authorization = `Basic ${credentials}`
} else {
throw new Error('Invalid authentication configuration')
}
return headers
}
export const listIndicesTool: ToolConfig<
ElasticsearchListIndicesParams,
ElasticsearchListIndicesResponse
> = {
id: 'elasticsearch_list_indices',
name: 'Elasticsearch List Indices',
description:
'List all indices in the Elasticsearch cluster with their health, status, and statistics.',
version: '1.0.0',
params: {
deploymentType: {
type: 'string',
required: true,
description: 'Deployment type: self_hosted or cloud',
},
host: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch host URL (for self-hosted)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elastic Cloud ID (for cloud deployments)',
},
authMethod: {
type: 'string',
required: true,
description: 'Authentication method: api_key or basic_auth',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch API key',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username for basic auth',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password for basic auth',
},
},
request: {
url: (params) => {
const baseUrl = buildBaseUrl(params)
return `${baseUrl}/_cat/indices?format=json`
},
method: 'GET',
headers: (params) => buildAuthHeaders(params),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
let errorMessage = `Elasticsearch error: ${response.status}`
try {
const errorJson = JSON.parse(errorText)
errorMessage = errorJson.error?.reason || errorJson.error?.type || errorMessage
} catch {
errorMessage = errorText || errorMessage
}
return {
success: false,
output: {
message: errorMessage,
indices: [],
},
error: errorMessage,
}
}
const data = await response.json()
const indices = data
.filter((item: Record<string, unknown>) => {
const indexName = item.index as string
return !indexName.startsWith('.')
})
.map((item: Record<string, unknown>) => ({
index: item.index as string,
health: item.health as string,
status: item.status as string,
docsCount: Number.parseInt(item['docs.count'] as string, 10) || 0,
storeSize: (item['store.size'] as string) || '0b',
primaryShards: Number.parseInt(item.pri as string, 10) || 0,
replicaShards: Number.parseInt(item.rep as string, 10) || 0,
}))
return {
success: true,
output: {
message: `Found ${indices.length} indices`,
indices,
},
}
},
outputs: {
message: {
type: 'string',
description: 'Summary message about the indices',
},
indices: {
type: 'json',
description: 'Array of index information objects',
},
},
}
+264
View File
@@ -0,0 +1,264 @@
import type {
ElasticsearchSearchParams,
ElasticsearchSearchResponse,
} from '@/tools/elasticsearch/types'
import type { ToolConfig } from '@/tools/types'
// Helper to build base URL from connection params
function buildBaseUrl(params: ElasticsearchSearchParams): string {
if (params.deploymentType === 'cloud' && params.cloudId) {
// Parse Cloud ID: format is "name:base64data"
// The base64 data contains: es_host$kibana_host ($ separated)
const parts = params.cloudId.split(':')
if (parts.length >= 2) {
try {
const decoded = Buffer.from(parts[1], 'base64').toString('utf-8')
const [esHost] = decoded.split('$')
if (esHost) {
// Cloud endpoints are always HTTPS with port 443
return `https://${parts[0]}.${esHost}`
}
} catch {
// If decoding fails, try using cloudId directly as host
}
}
throw new Error('Invalid Cloud ID format')
}
if (!params.host) {
throw new Error('Host is required for self-hosted deployments')
}
return params.host.replace(/\/$/, '') // Remove trailing slash
}
// Helper to build auth headers
function buildAuthHeaders(params: ElasticsearchSearchParams): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (params.authMethod === 'api_key' && params.apiKey) {
headers.Authorization = `ApiKey ${params.apiKey}`
} else if (params.authMethod === 'basic_auth' && params.username && params.password) {
const credentials = Buffer.from(`${params.username}:${params.password}`).toString('base64')
headers.Authorization = `Basic ${credentials}`
} else {
throw new Error('Invalid authentication configuration')
}
return headers
}
export const searchTool: ToolConfig<ElasticsearchSearchParams, ElasticsearchSearchResponse> = {
id: 'elasticsearch_search',
name: 'Elasticsearch Search',
description:
'Search documents in Elasticsearch using Query DSL. Returns matching documents with scores and metadata.',
version: '1.0.0',
params: {
deploymentType: {
type: 'string',
required: true,
description: 'Deployment type: self_hosted or cloud',
},
host: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch host URL (for self-hosted)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elastic Cloud ID (for cloud deployments)',
},
authMethod: {
type: 'string',
required: true,
description: 'Authentication method: api_key or basic_auth',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch API key',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username for basic auth',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password for basic auth',
},
index: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Index name to search (e.g., "products", "logs-2024")',
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Query DSL as JSON string. Example: {"match":{"title":"search term"}} or {"bool":{"must":[...]}}',
},
from: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Starting offset for pagination (e.g., 0, 10, 20). Default: 0',
},
size: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to return (e.g., 10, 25, 100). Default: 10',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Sort specification as JSON string. Example: [{"created_at":"desc"}] or [{"_score":"desc"},{"name":"asc"}]',
},
sourceIncludes: {
type: 'string',
required: false,
description: 'Comma-separated list of fields to include in _source',
},
sourceExcludes: {
type: 'string',
required: false,
description: 'Comma-separated list of fields to exclude from _source',
},
trackTotalHits: {
type: 'boolean',
required: false,
description: 'Track accurate total hit count (default: true)',
},
},
request: {
url: (params) => {
const baseUrl = buildBaseUrl(params)
return `${baseUrl}/${encodeURIComponent(params.index)}/_search`
},
method: 'POST',
headers: (params) => buildAuthHeaders(params),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.query) {
try {
body.query = JSON.parse(params.query)
} catch {
// If not valid JSON, treat as simple match query
body.query = { match_all: {} }
}
}
if (params.from !== undefined) body.from = params.from
if (params.size !== undefined) body.size = params.size
if (params.sort) {
try {
body.sort = JSON.parse(params.sort)
} catch {
// Ignore invalid sort
}
}
if (params.sourceIncludes || params.sourceExcludes) {
body._source = {}
if (params.sourceIncludes) {
;(body._source as Record<string, unknown>).includes = params.sourceIncludes
.split(',')
.map((s) => s.trim())
}
if (params.sourceExcludes) {
;(body._source as Record<string, unknown>).excludes = params.sourceExcludes
.split(',')
.map((s) => s.trim())
}
}
if (params.trackTotalHits !== undefined) {
body.track_total_hits = params.trackTotalHits
}
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
let errorMessage = `Elasticsearch error: ${response.status}`
try {
const errorJson = JSON.parse(errorText)
errorMessage = errorJson.error?.reason || errorJson.error?.type || errorMessage
} catch {
errorMessage = errorText || errorMessage
}
return {
success: false,
output: {
took: 0,
timed_out: false,
hits: { total: { value: 0, relation: 'eq' }, max_score: null, hits: [] },
},
error: errorMessage,
}
}
const data = await response.json()
return {
success: true,
output: {
took: data.took,
timed_out: data.timed_out,
hits: {
total: data.hits.total,
max_score: data.hits.max_score,
hits: data.hits.hits.map((hit: Record<string, unknown>) => ({
_index: hit._index,
_id: hit._id,
_score: hit._score,
_source: hit._source,
})),
},
aggregations: data.aggregations,
},
}
},
outputs: {
took: {
type: 'number',
description: 'Time in milliseconds the search took',
},
timed_out: {
type: 'boolean',
description: 'Whether the search timed out',
},
hits: {
type: 'object',
description: 'Search results with total count and matching documents',
},
aggregations: {
type: 'json',
description: 'Aggregation results if any',
optional: true,
},
},
}
+299
View File
@@ -0,0 +1,299 @@
// Common types for Elasticsearch tools
import type { ToolResponse } from '@/tools/types'
// Base params for all Elasticsearch tools
interface ElasticsearchBaseParams {
// Connection configuration
deploymentType: 'self_hosted' | 'cloud'
host?: string // For self-hosted
cloudId?: string // For Elastic Cloud
// Authentication
authMethod: 'api_key' | 'basic_auth'
apiKey?: string
username?: string
password?: string
}
// Document Operations
export interface ElasticsearchIndexDocumentParams extends ElasticsearchBaseParams {
index: string
documentId?: string
document: string // JSON string
refresh?: 'true' | 'false' | 'wait_for'
}
export interface ElasticsearchGetDocumentParams extends ElasticsearchBaseParams {
index: string
documentId: string
sourceIncludes?: string
sourceExcludes?: string
}
export interface ElasticsearchUpdateDocumentParams extends ElasticsearchBaseParams {
index: string
documentId: string
document: string // JSON string (partial document)
retryOnConflict?: number
}
export interface ElasticsearchDeleteDocumentParams extends ElasticsearchBaseParams {
index: string
documentId: string
refresh?: 'true' | 'false' | 'wait_for'
}
export interface ElasticsearchBulkParams extends ElasticsearchBaseParams {
index?: string
operations: string // NDJSON string
refresh?: 'true' | 'false' | 'wait_for'
}
// Search Operations
export interface ElasticsearchSearchParams extends ElasticsearchBaseParams {
index: string
query?: string // JSON string
from?: number
size?: number
sort?: string // JSON string
sourceIncludes?: string
sourceExcludes?: string
trackTotalHits?: boolean
}
export interface ElasticsearchCountParams extends ElasticsearchBaseParams {
index: string
query?: string // JSON string
}
// Index Management Operations
export interface ElasticsearchCreateIndexParams extends ElasticsearchBaseParams {
index: string
settings?: string // JSON string
mappings?: string // JSON string
}
export interface ElasticsearchDeleteIndexParams extends ElasticsearchBaseParams {
index: string
}
export interface ElasticsearchGetIndexParams extends ElasticsearchBaseParams {
index: string
}
interface ElasticsearchIndexExistsParams extends ElasticsearchBaseParams {
index: string
}
interface ElasticsearchRefreshIndexParams extends ElasticsearchBaseParams {
index: string
}
interface ElasticsearchIndexStatsParams extends ElasticsearchBaseParams {
index: string
}
// Mapping Operations
interface ElasticsearchPutMappingParams extends ElasticsearchBaseParams {
index: string
mappings: string // JSON string
}
interface ElasticsearchGetMappingParams extends ElasticsearchBaseParams {
index: string
}
// Cluster Operations
export interface ElasticsearchClusterHealthParams extends ElasticsearchBaseParams {
waitForStatus?: 'green' | 'yellow' | 'red'
timeout?: string
}
export interface ElasticsearchClusterStatsParams extends ElasticsearchBaseParams {}
export interface ElasticsearchListIndicesParams extends ElasticsearchBaseParams {}
interface ElasticsearchIndexInfo {
index: string
health: string
status: string
docsCount: number
storeSize: string
primaryShards: number
replicaShards: number
}
// Response types
export interface ElasticsearchDocumentResponse extends ToolResponse {
output: {
_index: string
_id: string
_version?: number
result?: 'created' | 'updated' | 'deleted' | 'not_found' | 'noop'
_source?: Record<string, unknown>
found?: boolean
}
}
export interface ElasticsearchSearchResponse extends ToolResponse {
output: {
took: number
timed_out: boolean
hits: {
total: { value: number; relation: string }
max_score: number | null
hits: Array<{
_index: string
_id: string
_score: number | null
_source: Record<string, unknown>
}>
}
aggregations?: Record<string, unknown>
}
}
export interface ElasticsearchCountResponse extends ToolResponse {
output: {
count: number
_shards: {
total: number
successful: number
skipped: number
failed: number
}
}
}
export interface ElasticsearchBulkResponse extends ToolResponse {
output: {
took: number
errors: boolean
items: Array<{
index?: { _index: string; _id: string; result: string; status: number }
create?: { _index: string; _id: string; result: string; status: number }
update?: { _index: string; _id: string; result: string; status: number }
delete?: { _index: string; _id: string; result: string; status: number }
}>
}
}
export interface ElasticsearchIndexResponse extends ToolResponse {
output: {
acknowledged: boolean
shards_acknowledged?: boolean
index?: string
}
}
export interface ElasticsearchIndexInfoResponse extends ToolResponse {
output: Record<
string,
{
aliases: Record<string, unknown>
mappings: Record<string, unknown>
settings: Record<string, unknown>
}
>
}
interface ElasticsearchIndexExistsResponse extends ToolResponse {
output: {
exists: boolean
}
}
interface ElasticsearchMappingResponse extends ToolResponse {
output: Record<string, { mappings: Record<string, unknown> }>
}
export interface ElasticsearchClusterHealthResponse extends ToolResponse {
output: {
cluster_name: string
status: 'green' | 'yellow' | 'red'
timed_out: boolean
number_of_nodes: number
number_of_data_nodes: number
active_primary_shards: number
active_shards: number
relocating_shards: number
initializing_shards: number
unassigned_shards: number
delayed_unassigned_shards: number
number_of_pending_tasks: number
number_of_in_flight_fetch: number
task_max_waiting_in_queue_millis: number
active_shards_percent_as_number: number
}
}
export interface ElasticsearchClusterStatsResponse extends ToolResponse {
output: {
cluster_name: string
cluster_uuid: string
status: string
nodes: {
count: { total: number; data: number; master: number }
versions: string[]
}
indices: {
count: number
docs: { count: number; deleted: number }
store: { size_in_bytes: number }
shards: { total: number; primaries: number }
}
}
}
interface ElasticsearchRefreshResponse extends ToolResponse {
output: {
_shards: {
total: number
successful: number
failed: number
}
}
}
interface ElasticsearchIndexStatsResponse extends ToolResponse {
output: {
_all: {
primaries: {
docs: { count: number; deleted: number }
store: { size_in_bytes: number }
indexing: { index_total: number }
search: { query_total: number }
}
total: {
docs: { count: number; deleted: number }
store: { size_in_bytes: number }
indexing: { index_total: number }
search: { query_total: number }
}
}
indices: Record<string, unknown>
}
}
export interface ElasticsearchListIndicesResponse extends ToolResponse {
output: {
message: string
indices: ElasticsearchIndexInfo[]
}
error?: string
}
// Union type for all Elasticsearch responses
export type ElasticsearchResponse =
| ElasticsearchDocumentResponse
| ElasticsearchSearchResponse
| ElasticsearchCountResponse
| ElasticsearchBulkResponse
| ElasticsearchIndexResponse
| ElasticsearchIndexInfoResponse
| ElasticsearchIndexExistsResponse
| ElasticsearchMappingResponse
| ElasticsearchClusterHealthResponse
| ElasticsearchClusterStatsResponse
| ElasticsearchRefreshResponse
| ElasticsearchIndexStatsResponse
| ElasticsearchListIndicesResponse
@@ -0,0 +1,192 @@
import type {
ElasticsearchDocumentResponse,
ElasticsearchUpdateDocumentParams,
} from '@/tools/elasticsearch/types'
import type { ToolConfig } from '@/tools/types'
function buildBaseUrl(params: ElasticsearchUpdateDocumentParams): string {
if (params.deploymentType === 'cloud' && params.cloudId) {
const parts = params.cloudId.split(':')
if (parts.length >= 2) {
try {
const decoded = Buffer.from(parts[1], 'base64').toString('utf-8')
const [esHost] = decoded.split('$')
if (esHost) {
return `https://${parts[0]}.${esHost}`
}
} catch {
// Fallback
}
}
throw new Error('Invalid Cloud ID format')
}
if (!params.host) {
throw new Error('Host is required for self-hosted deployments')
}
return params.host.replace(/\/$/, '')
}
function buildAuthHeaders(params: ElasticsearchUpdateDocumentParams): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (params.authMethod === 'api_key' && params.apiKey) {
headers.Authorization = `ApiKey ${params.apiKey}`
} else if (params.authMethod === 'basic_auth' && params.username && params.password) {
const credentials = Buffer.from(`${params.username}:${params.password}`).toString('base64')
headers.Authorization = `Basic ${credentials}`
} else {
throw new Error('Invalid authentication configuration')
}
return headers
}
export const updateDocumentTool: ToolConfig<
ElasticsearchUpdateDocumentParams,
ElasticsearchDocumentResponse
> = {
id: 'elasticsearch_update_document',
name: 'Elasticsearch Update Document',
description: 'Partially update a document in Elasticsearch using doc merge.',
version: '1.0.0',
params: {
deploymentType: {
type: 'string',
required: true,
description: 'Deployment type: self_hosted or cloud',
},
host: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch host URL (for self-hosted)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elastic Cloud ID (for cloud deployments)',
},
authMethod: {
type: 'string',
required: true,
description: 'Authentication method: api_key or basic_auth',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch API key',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username for basic auth',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password for basic auth',
},
index: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Index name (e.g., "products", "logs-2024")',
},
documentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Document ID to update (e.g., "abc123", "user_456")',
},
document: {
type: 'string',
required: true,
description: 'Partial document to merge as JSON string',
},
retryOnConflict: {
type: 'number',
required: false,
description: 'Number of retries on version conflict',
},
},
request: {
url: (params) => {
const baseUrl = buildBaseUrl(params)
let url = `${baseUrl}/${encodeURIComponent(params.index)}/_update/${encodeURIComponent(params.documentId)}`
if (params.retryOnConflict !== undefined) {
url += `?retry_on_conflict=${params.retryOnConflict}`
}
return url
},
method: 'POST',
headers: (params) => buildAuthHeaders(params),
body: (params) => {
try {
return { doc: JSON.parse(params.document) }
} catch {
throw new Error('Invalid JSON document')
}
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
let errorMessage = `Elasticsearch error: ${response.status}`
try {
const errorJson = JSON.parse(errorText)
errorMessage = errorJson.error?.reason || errorJson.error?.type || errorMessage
} catch {
errorMessage = errorText || errorMessage
}
return {
success: false,
output: { _index: '', _id: '' },
error: errorMessage,
}
}
const data = await response.json()
return {
success: true,
output: {
_index: data._index,
_id: data._id,
_version: data._version,
result: data.result,
},
}
},
outputs: {
_index: {
type: 'string',
description: 'Index name',
},
_id: {
type: 'string',
description: 'Document ID',
},
_version: {
type: 'number',
description: 'New document version',
},
result: {
type: 'string',
description: 'Operation result (updated or noop)',
},
},
}