Skip to content

Commit 264fcca

Browse files
committed
fix(elasticsearch): correct get_index output, cluster-health timeout, and redirect credentials
1 parent 84654ff commit 264fcca

20 files changed

Lines changed: 344 additions & 36 deletions

‎apps/docs/content/docs/integrations/elasticsearch.mdx‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ Retrieve index information including settings, mappings, and aliases.
306306

307307
| Parameter | Type | Description |
308308
| --------- | ---- | ----------- |
309-
| `index` | json | Index information including aliases, mappings, and settings |
309+
| `indices` | json | Matched indices keyed by index name, each with its aliases, mappings, and settings |
310310

311311
### Elasticsearch Cluster Health
312312

@@ -324,7 +324,7 @@ Get the health status of the Elasticsearch cluster.
324324
| `username` | string | No | Username for basic auth |
325325
| `password` | string | No | Password for basic auth |
326326
| `waitForStatus` | string | No | Wait until cluster reaches this status: green, yellow, or red |
327-
| `timeout` | string | No | Timeout for the wait operation \(e.g., 30s, 1m\) |
327+
| `clusterTimeout` | string | No | How long Elasticsearch waits for the cluster to reach the requested status, as an Elasticsearch time value \(e.g., 30s, 1m\). Not named "timeout": that name is reserved by the tool transport as a client-side abort deadline in milliseconds. |
328328

329329
#### Output
330330

@@ -377,12 +377,13 @@ List all indices in the Elasticsearch cluster with their health, status, and sta
377377
| `apiKey` | string | No | Elasticsearch API key |
378378
| `username` | string | No | Username for basic auth |
379379
| `password` | string | No | Password for basic auth |
380+
| `includeSystemIndices` | boolean | No | Include Elasticsearch system indices \(names starting with "."\). Omitted by default. |
380381

381382
#### Output
382383

383384
| Parameter | Type | Description |
384385
| --------- | ---- | ----------- |
385386
| `message` | string | Summary message about the indices |
386-
| `indices` | json | Array of index information objects |
387+
| `indices` | json | Array of index information objects \(index, health, status, docsCount, storeSize, primaryShards, replicaShards\). System indices are omitted unless includeSystemIndices is set. |
387388

388389

‎apps/sim/blocks/blocks/elasticsearch.ts‎

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -483,15 +483,32 @@ Return ONLY valid JSON - no explanations, no markdown code blocks.`,
483483
condition: { field: 'operation', value: 'elasticsearch_cluster_health' },
484484
},
485485

486-
// Cluster health timeout
486+
// Cluster health timeout. The subBlock id stays `timeout` so saved workflow
487+
// state keeps resolving; `tools.config.params` remaps it to `clusterTimeout`
488+
// and clears the transport's reserved `timeout` key.
487489
{
488490
id: 'timeout',
489-
title: 'Timeout (seconds)',
491+
title: 'Timeout',
490492
type: 'short-input',
491-
placeholder: '30',
493+
placeholder: '30s',
494+
mode: 'advanced',
492495
condition: { field: 'operation', value: 'elasticsearch_cluster_health' },
493496
},
494497

498+
// Include system indices
499+
{
500+
id: 'includeSystemIndices',
501+
title: 'Include System Indices',
502+
type: 'dropdown',
503+
options: [
504+
{ label: 'No', id: '' },
505+
{ label: 'Yes', id: 'true' },
506+
],
507+
value: () => '',
508+
mode: 'advanced',
509+
condition: { field: 'operation', value: 'elasticsearch_list_indices' },
510+
},
511+
495512
// Retry on conflict
496513
{
497514
id: 'retryOnConflict',
@@ -528,9 +545,15 @@ Return ONLY valid JSON - no explanations, no markdown code blocks.`,
528545
if (params.size) result.size = Number(params.size)
529546
if (params.from) result.from = Number(params.from)
530547
if (params.retryOnConflict) result.retryOnConflict = Number(params.retryOnConflict)
531-
if (params.timeout && typeof params.timeout === 'string') {
532-
result.timeout = params.timeout.endsWith('s') ? params.timeout : `${params.timeout}s`
548+
549+
if (params.includeSystemIndices === 'true') result.includeSystemIndices = true
550+
551+
const rawTimeout = typeof params.timeout === 'string' ? params.timeout.trim() : ''
552+
if (rawTimeout) {
553+
result.clusterTimeout = /^\d+$/.test(rawTimeout) ? `${rawTimeout}s` : rawTimeout
533554
}
555+
result.timeout = undefined
556+
534557
return result
535558
},
536559
},
@@ -559,7 +582,14 @@ Return ONLY valid JSON - no explanations, no markdown code blocks.`,
559582
mappings: { type: 'string', description: 'Index mappings as JSON' },
560583
refresh: { type: 'string', description: 'Refresh policy' },
561584
waitForStatus: { type: 'string', description: 'Wait for cluster status' },
562-
timeout: { type: 'string', description: 'Timeout for wait operations' },
585+
timeout: {
586+
type: 'string',
587+
description: 'How long Elasticsearch waits for the cluster to reach the requested status',
588+
},
589+
includeSystemIndices: {
590+
type: 'string',
591+
description: 'Include Elasticsearch system indices when listing',
592+
},
563593
retryOnConflict: { type: 'number', description: 'Retry attempts on conflict' },
564594
},
565595

@@ -581,8 +611,14 @@ Return ONLY valid JSON - no explanations, no markdown code blocks.`,
581611
items: { type: 'json', description: 'Bulk operation results' },
582612
// Count outputs
583613
count: { type: 'number', description: 'Document count' },
614+
_shards: {
615+
type: 'json',
616+
description: 'Shard statistics (total, successful, skipped, failed)',
617+
},
584618
// Index outputs
585619
acknowledged: { type: 'boolean', description: 'Whether operation was acknowledged' },
620+
// List indices outputs
621+
message: { type: 'string', description: 'Summary message about the indices listed' },
586622
// Cluster outputs
587623
cluster_name: { type: 'string', description: 'Cluster name' },
588624
status: { type: 'string', description: 'Cluster health status' },

‎apps/sim/tools/elasticsearch/bulk.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export const bulkTool: ToolConfig<ElasticsearchBulkParams, ElasticsearchBulkResp
8888
},
8989
method: 'POST',
9090
headers: (params) => buildAuthHeaders(params, 'application/x-ndjson'),
91+
redirectPolicy: () => ({ mode: 'legacy', sendCredentialsOnCrossOriginRedirect: false }),
9192
body: (params) => {
9293
// The body should be NDJSON format - we pass it as raw string
9394
// Ensure it ends with a newline
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { describe, expect, it } from 'vitest'
5+
import { ElasticsearchBlock } from '@/blocks/blocks/elasticsearch'
6+
import * as elasticsearchTools from '@/tools/elasticsearch'
7+
import { prepareToolRequest } from '@/tools/request-transport'
8+
import type { ToolConfig } from '@/tools/types'
9+
10+
const CONNECTION = {
11+
deploymentType: 'self_hosted',
12+
host: 'https://es.example.com',
13+
authMethod: 'api_key',
14+
apiKey: 'test-key',
15+
} as const
16+
17+
function mapBlockParams(params: Record<string, unknown>): Record<string, unknown> {
18+
const config = ElasticsearchBlock.tools.config
19+
if (!config?.params) throw new Error('block has no params mapper')
20+
return config.params(params) as Record<string, unknown>
21+
}
22+
23+
/**
24+
* `tools/request-transport.ts` reads `params.timeout` as the outbound HTTP
25+
* deadline in milliseconds. A tool param of that name therefore arms a client
26+
* abort as a side effect of asking Elasticsearch to wait.
27+
*/
28+
describe('cluster health timeout does not arm a client abort', () => {
29+
it('sends the wait on the wire and leaves no client deadline', () => {
30+
const prepared = prepareToolRequest(
31+
elasticsearchTools.elasticsearchClusterHealthTool as ToolConfig,
32+
{ ...CONNECTION, clusterTimeout: '30s', waitForStatus: 'yellow' }
33+
)
34+
expect(prepared.url).toContain('timeout=30s')
35+
expect(prepared.url).toContain('wait_for_status=yellow')
36+
expect(prepared.timeout).toBeUndefined()
37+
})
38+
39+
it('ignores a stray transport timeout left in saved state', () => {
40+
const mapped = mapBlockParams({ operation: 'elasticsearch_cluster_health', timeout: '30' })
41+
expect(mapped.timeout).toBeUndefined()
42+
expect(mapped.clusterTimeout).toBe('30s')
43+
})
44+
45+
it('declares no param named timeout', () => {
46+
expect(
47+
Object.keys(elasticsearchTools.elasticsearchClusterHealthTool.params ?? {})
48+
).not.toContain('timeout')
49+
})
50+
})
51+
52+
describe('cluster health timeout units', () => {
53+
it.each([
54+
['30', '30s'],
55+
['30s', '30s'],
56+
['1m', '1m'],
57+
['500ms', '500ms'],
58+
['2h', '2h'],
59+
])('maps %o to %o', (input, expected) => {
60+
const mapped = mapBlockParams({ operation: 'elasticsearch_cluster_health', timeout: input })
61+
expect(mapped.clusterTimeout).toBe(expected)
62+
})
63+
64+
it('emits nothing for a blank timeout', () => {
65+
expect(
66+
mapBlockParams({ operation: 'elasticsearch_cluster_health', timeout: ' ' })
67+
).not.toHaveProperty('clusterTimeout')
68+
})
69+
})
70+
71+
describe('list indices system-index opt-in', () => {
72+
it('coerces the dropdown string to a real boolean', () => {
73+
expect(
74+
mapBlockParams({ operation: 'elasticsearch_list_indices', includeSystemIndices: 'true' })
75+
).toMatchObject({ includeSystemIndices: true })
76+
})
77+
78+
it('omits the flag when the dropdown is left at its default', () => {
79+
expect(
80+
mapBlockParams({ operation: 'elasticsearch_list_indices', includeSystemIndices: '' })
81+
).not.toHaveProperty('includeSystemIndices')
82+
})
83+
})

‎apps/sim/tools/elasticsearch/cluster_health.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,11 @@ export const clusterHealthTool: ToolConfig<
6060
required: false,
6161
description: 'Wait until cluster reaches this status: green, yellow, or red',
6262
},
63-
timeout: {
63+
clusterTimeout: {
6464
type: 'string',
6565
required: false,
66-
description: 'Timeout for the wait operation (e.g., 30s, 1m)',
66+
description:
67+
'How long Elasticsearch waits for the cluster to reach the requested status, as an Elasticsearch time value (e.g., 30s, 1m). Not named "timeout": that name is reserved by the tool transport as a client-side abort deadline in milliseconds.',
6768
},
6869
},
6970

@@ -76,8 +77,8 @@ export const clusterHealthTool: ToolConfig<
7677
if (params.waitForStatus) {
7778
queryParams.push(`wait_for_status=${params.waitForStatus}`)
7879
}
79-
if (params.timeout) {
80-
queryParams.push(`timeout=${encodeURIComponent(params.timeout)}`)
80+
if (params.clusterTimeout) {
81+
queryParams.push(`timeout=${encodeURIComponent(params.clusterTimeout)}`)
8182
}
8283
if (queryParams.length > 0) {
8384
url += `?${queryParams.join('&')}`
@@ -87,6 +88,7 @@ export const clusterHealthTool: ToolConfig<
8788
},
8889
method: 'GET',
8990
headers: (params) => buildAuthHeaders(params),
91+
redirectPolicy: () => ({ mode: 'legacy', sendCredentialsOnCrossOriginRedirect: false }),
9092
},
9193

9294
transformResponse: async (response: Response) => {

‎apps/sim/tools/elasticsearch/cluster_stats.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export const clusterStatsTool: ToolConfig<
6464
},
6565
method: 'GET',
6666
headers: (params) => buildAuthHeaders(params),
67+
redirectPolicy: () => ({ mode: 'legacy', sendCredentialsOnCrossOriginRedirect: false }),
6768
},
6869

6970
transformResponse: async (response: Response) => {
@@ -127,6 +128,7 @@ export const clusterStatsTool: ToolConfig<
127128
status: {
128129
type: 'string',
129130
description: 'Cluster health status',
131+
optional: true,
130132
},
131133
nodes: {
132134
type: 'object',

‎apps/sim/tools/elasticsearch/count.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export const countTool: ToolConfig<ElasticsearchCountParams, ElasticsearchCountR
7474
},
7575
method: 'POST',
7676
headers: (params) => buildAuthHeaders(params),
77+
redirectPolicy: () => ({ mode: 'legacy', sendCredentialsOnCrossOriginRedirect: false }),
7778
body: (params) => {
7879
if (params.query) {
7980
try {

‎apps/sim/tools/elasticsearch/create_index.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export const createIndexTool: ToolConfig<
8080
},
8181
method: 'PUT',
8282
headers: (params) => buildAuthHeaders(params),
83+
redirectPolicy: () => ({ mode: 'legacy', sendCredentialsOnCrossOriginRedirect: false }),
8384
body: (params) => {
8485
const body: Record<string, unknown> = {}
8586

@@ -140,10 +141,12 @@ export const createIndexTool: ToolConfig<
140141
shards_acknowledged: {
141142
type: 'boolean',
142143
description: 'Whether the shards were acknowledged',
144+
optional: true,
143145
},
144146
index: {
145147
type: 'string',
146148
description: 'Created index name',
149+
optional: true,
147150
},
148151
},
149152
}

‎apps/sim/tools/elasticsearch/delete_document.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export const deleteDocumentTool: ToolConfig<
8787
},
8888
method: 'DELETE',
8989
headers: (params) => buildAuthHeaders(params),
90+
redirectPolicy: () => ({ mode: 'legacy', sendCredentialsOnCrossOriginRedirect: false }),
9091
},
9192

9293
transformResponse: async (response: Response) => {
@@ -142,6 +143,7 @@ export const deleteDocumentTool: ToolConfig<
142143
_version: {
143144
type: 'number',
144145
description: 'Document version',
146+
optional: true,
145147
},
146148
result: {
147149
type: 'string',

‎apps/sim/tools/elasticsearch/delete_index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export const deleteIndexTool: ToolConfig<
7070
},
7171
method: 'DELETE',
7272
headers: (params) => buildAuthHeaders(params),
73+
redirectPolicy: () => ({ mode: 'legacy', sendCredentialsOnCrossOriginRedirect: false }),
7374
},
7475

7576
transformResponse: async (response: Response) => {

0 commit comments

Comments
 (0)