Skip to content

Commit 6d1eeb8

Browse files
authored
feat(agentFlow) - Add support for listFlows (#6005)
* Add support for listFlows * Fix gemini comments
1 parent 52e1408 commit 6d1eeb8

4 files changed

Lines changed: 85 additions & 1 deletion

File tree

packages/agentflow/src/infrastructure/api/hooks/useAsyncOptions.test.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import { useAsyncOptions } from './useAsyncOptions'
77
const mockGetChatModels = jest.fn()
88
const mockGetAllTools = jest.fn()
99
const mockGetCredentialsByName = jest.fn()
10+
const mockGetAllChatflows = jest.fn()
1011

1112
// Stable API objects — same reference on every render so they don't re-trigger the effect
1213
const mockApiContext = {
14+
chatflowsApi: { getAllChatflows: mockGetAllChatflows },
1315
chatModelsApi: { getChatModels: mockGetChatModels },
1416
toolsApi: { getAllTools: mockGetAllTools },
1517
credentialsApi: { getAllCredentials: jest.fn(), getCredentialsByName: mockGetCredentialsByName },
@@ -94,6 +96,26 @@ describe('useAsyncOptions', () => {
9496
expect(result.current.options[0]).toEqual({ label: 'OpenAI Key', name: 'c1' })
9597
})
9698

99+
it('listFlows: populates options with label/name/description mapped from chatflows', async () => {
100+
mockGetAllChatflows.mockResolvedValue([
101+
{ id: 'cf-1', name: 'Support Bot', type: 'CHATFLOW' },
102+
{ id: 'cf-2', name: 'Sales Agent', type: 'AGENTFLOW' },
103+
{ id: 'cf-3', name: 'Multi Agent', type: 'MULTIAGENT' }
104+
])
105+
106+
const { result } = renderHook(() => useAsyncOptions({ loadMethod: 'listFlows' }))
107+
108+
await waitFor(() => expect(result.current.loading).toBe(false))
109+
110+
expect(mockGetAllChatflows).toHaveBeenCalledTimes(1)
111+
expect(result.current.options).toEqual([
112+
{ name: 'cf-1', label: 'Support Bot', description: 'Chatflow' },
113+
{ name: 'cf-2', label: 'Sales Agent', description: 'Agentflow V2' },
114+
{ name: 'cf-3', label: 'Multi Agent', description: 'Agentflow V1' }
115+
])
116+
expect(result.current.error).toBeNull()
117+
})
118+
97119
it('API error: sets error message, loading false', async () => {
98120
mockGetChatModels.mockRejectedValue(new Error('Network failure'))
99121

packages/agentflow/src/infrastructure/api/hooks/useAsyncOptions.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ interface UseAsyncOptionsResult {
2525
* Fetches async option lists from the API using the loadMethodRegistry.
2626
*/
2727
export function useAsyncOptions({ loadMethod, credentialNames, params }: UseAsyncOptionsParams): UseAsyncOptionsResult {
28-
const { chatModelsApi, toolsApi, credentialsApi, storesApi, embeddingsApi, runtimeStateApi, nodesApi, apiBaseUrl } = useApiContext()
28+
const { chatflowsApi, chatModelsApi, toolsApi, credentialsApi, storesApi, embeddingsApi, runtimeStateApi, nodesApi, apiBaseUrl } =
29+
useApiContext()
2930

3031
const [options, setOptions] = useState<OptionItem[]>([])
3132
const [loading, setLoading] = useState(true)
@@ -65,6 +66,7 @@ export function useAsyncOptions({ loadMethod, credentialNames, params }: UseAsyn
6566
} else if (loadMethod) {
6667
const fn = getLoadMethod(loadMethod)
6768
const apis: ApiServices = {
69+
chatflowsApi,
6870
chatModelsApi,
6971
toolsApi,
7072
credentialsApi,
@@ -102,6 +104,7 @@ export function useAsyncOptions({ loadMethod, credentialNames, params }: UseAsyn
102104
credentialNamesKey,
103105
paramsKey,
104106
fetchCounter,
107+
chatflowsApi,
105108
chatModelsApi,
106109
toolsApi,
107110
credentialsApi,

packages/agentflow/src/infrastructure/api/loadMethodRegistry.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@ import type { ApiServices } from './loadMethodRegistry'
22
import { getLoadMethod, loadMethodRegistry } from './loadMethodRegistry'
33

44
const mockApis: ApiServices = {
5+
chatflowsApi: {
6+
getAllChatflows: jest.fn(),
7+
getChatflow: jest.fn(),
8+
createChatflow: jest.fn(),
9+
updateChatflow: jest.fn(),
10+
deleteChatflow: jest.fn(),
11+
generateAgentflow: jest.fn(),
12+
getChatModels: jest.fn()
13+
},
514
chatModelsApi: {
615
getChatModels: jest.fn()
716
},
@@ -159,6 +168,39 @@ describe('loadMethodRegistry', () => {
159168
})
160169
})
161170

171+
describe('listFlows', () => {
172+
it('should call chatflowsApi.getAllChatflows() and map to label/name/description', async () => {
173+
const mockChatflows = [
174+
{ id: 'cf-1', name: 'My Chatflow', type: 'CHATFLOW' },
175+
{ id: 'cf-2', name: 'My Agentflow', type: 'AGENTFLOW' },
176+
{ id: 'cf-3', name: 'My Multi', type: 'MULTIAGENT' }
177+
]
178+
;(mockApis.chatflowsApi.getAllChatflows as jest.Mock).mockResolvedValue(mockChatflows)
179+
180+
const result = await loadMethodRegistry['listFlows'](mockApis)
181+
expect(mockApis.chatflowsApi.getAllChatflows).toHaveBeenCalled()
182+
expect(result).toEqual([
183+
{ label: 'My Chatflow', name: 'cf-1', description: 'Chatflow' },
184+
{ label: 'My Agentflow', name: 'cf-2', description: 'Agentflow V2' },
185+
{ label: 'My Multi', name: 'cf-3', description: 'Agentflow V1' }
186+
])
187+
})
188+
189+
it('should return empty array when there are no chatflows', async () => {
190+
;(mockApis.chatflowsApi.getAllChatflows as jest.Mock).mockResolvedValue([])
191+
192+
const result = await loadMethodRegistry['listFlows'](mockApis)
193+
expect(result).toEqual([])
194+
})
195+
196+
it('should fall back to "Chatflow" description for unknown type', async () => {
197+
;(mockApis.chatflowsApi.getAllChatflows as jest.Mock).mockResolvedValue([{ id: 'cf-1', name: 'Unknown', type: 'UNKNOWN' }])
198+
199+
const result = await loadMethodRegistry['listFlows'](mockApis)
200+
expect(result).toEqual([{ label: 'Unknown', name: 'cf-1', description: 'Chatflow' }])
201+
})
202+
})
203+
162204
describe('listCredentials', () => {
163205
it('should call credentialsApi.getCredentialsByName() with params.name', async () => {
164206
const mockCredentials = [{ id: '1', name: 'My Key', credentialName: 'openAIApi' }]

packages/agentflow/src/infrastructure/api/loadMethodRegistry.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { ChatflowsApi } from './chatflows'
12
import type { CredentialsApi } from './credentials'
23
import type { EmbeddingsApi } from './embeddings'
34
import type { ChatModelsApi } from './models'
@@ -7,6 +8,7 @@ import type { StoresApi } from './stores'
78
import type { ToolsApi } from './tools'
89

910
export interface ApiServices {
11+
chatflowsApi: ChatflowsApi
1012
chatModelsApi: ChatModelsApi
1113
toolsApi: ToolsApi
1214
credentialsApi: CredentialsApi
@@ -31,13 +33,20 @@ export interface ApiServices {
3133
* - `listVectorStores` — fetches vector stores via `POST /node-load-method/agentAgentflow`
3234
* - `listEmbeddings` — fetches embedding models via `POST /node-load-method/agentAgentflow`
3335
* - `listRuntimeStateKeys` — fetches runtime state keys via `POST /node-load-method/agentAgentflow`
36+
* - `listFlows` — fetches all chatflows/agentflows via `GET /chatflows`; returns `{ label: name, name: id, description: type }`
3437
* - `listCredentials` — fetches credentials filtered by `params.name` via `GET /credentials?credentialName=<name>`
3538
* - `listActions` — fetches available actions for a node (e.g. Composio, MCP tools) via `POST /node-load-method/{nodeName}`;
3639
* requires `params.nodeName` and accepts optional `params.inputs` forwarded as `currentNode.inputs`
3740
* - `listTables` — fetches available tables for a node (e.g. AWSDynamoDBKVStorage) via `POST /node-load-method/{nodeName}`;
3841
* requires `params.nodeName` and accepts optional `params.inputs` forwarded as `currentNode.inputs`
3942
*
4043
*/
44+
function getChatflowTypeLabel(type: string | undefined): string {
45+
if (type === 'AGENTFLOW') return 'Agentflow V2'
46+
if (type === 'MULTIAGENT') return 'Agentflow V1'
47+
return 'Chatflow'
48+
}
49+
4150
export const loadMethodRegistry: Record<string, (_apis: ApiServices, _params?: Record<string, unknown>) => Promise<unknown>> = {
4251
listModels: (apis, params) => {
4352
const nodeName = params?.nodeName as string | undefined
@@ -53,6 +62,14 @@ export const loadMethodRegistry: Record<string, (_apis: ApiServices, _params?: R
5362
listVectorStores: (apis) => apis.storesApi.getVectorStores(),
5463
listEmbeddings: (apis) => apis.embeddingsApi.getEmbeddings(),
5564
listRuntimeStateKeys: (apis) => apis.runtimeStateApi.getRuntimeStateKeys(),
65+
listFlows: async (apis) => {
66+
const chatflows = await apis.chatflowsApi.getAllChatflows()
67+
return chatflows.map((cf) => ({
68+
label: cf.name,
69+
name: cf.id,
70+
description: getChatflowTypeLabel(cf.type)
71+
}))
72+
},
5673
listCredentials: (apis, params) => {
5774
const name = params?.name
5875
if (typeof name !== 'string') {

0 commit comments

Comments
 (0)