Skip to content

Commit 51ac34e

Browse files
committed
Merge branch 'chore/Langchain-V1' of https://github.com/FlowiseAI/Flowise into chore/Langchain-V1
2 parents e9d3936 + eafd93a commit 51ac34e

14 files changed

Lines changed: 382 additions & 14 deletions

File tree

packages/agentflow/src/core/types/api.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import type { InternalAxiosRequestConfig } from 'axios'
66

7+
import type { NodeData } from './node'
8+
79
export type RequestInterceptor = (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig
810

911
export interface Chatflow {
@@ -23,3 +25,20 @@ export interface ApiResponse<T> {
2325
data: T
2426
status: number
2527
}
28+
29+
export type ChatModel = Omit<NodeData, 'id'>
30+
31+
export interface Tool {
32+
label: string
33+
name: string
34+
imageSrc?: string
35+
}
36+
37+
export interface Credential {
38+
id: string
39+
name: string
40+
credentialName: string
41+
createdDate?: string
42+
updatedDate?: string
43+
workspaceID?: string
44+
}

packages/agentflow/src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ export { useAgentflow } from './useAgentflow'
1414
// Context hooks (for advanced usage)
1515
export { useAgentflowContext, useApiContext, useConfigContext } from './infrastructure/store'
1616

17+
// Load method registry (for dynamic API dispatch from node input loadMethod strings)
18+
export type { ApiServices } from './infrastructure/api'
19+
export { getLoadMethod } from './infrastructure/api'
20+
1721
// Types
22+
/* eslint-disable simple-import-sort/exports */
1823
export type {
1924
// Instance
2025
AgentFlowInstance,
@@ -24,6 +29,9 @@ export type {
2429
// API
2530
ApiResponse,
2631
Chatflow,
32+
ChatModel,
33+
Credential,
34+
Tool,
2735
// Context
2836
ConfigContextValue,
2937
// Flow data
@@ -48,6 +56,7 @@ export type {
4856
ValidationResult,
4957
Viewport
5058
} from './core/types'
59+
/* eslint-enable simple-import-sort/exports */
5160

5261
// Utilities (for advanced usage)
5362
export { filterNodesByComponents, isAgentflowNode } from './core/node-catalog'
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { AxiosInstance } from 'axios'
2+
3+
import { bindCredentialsApi } from './credentials'
4+
5+
const mockClient = {
6+
get: jest.fn()
7+
} as unknown as jest.Mocked<AxiosInstance>
8+
9+
beforeEach(() => {
10+
jest.clearAllMocks()
11+
})
12+
13+
describe('bindCredentialsApi', () => {
14+
const api = bindCredentialsApi(mockClient)
15+
16+
describe('getAllCredentials', () => {
17+
it('should call GET /credentials', async () => {
18+
const mockCredentials = [{ id: '1', name: 'My OpenAI Key', credentialName: 'openAIApi' }]
19+
;(mockClient.get as jest.Mock).mockResolvedValue({ data: mockCredentials })
20+
21+
const result = await api.getAllCredentials()
22+
expect(mockClient.get).toHaveBeenCalledWith('/credentials')
23+
expect(result).toEqual(mockCredentials)
24+
})
25+
})
26+
27+
describe('getCredentialsByName', () => {
28+
it('should call GET /credentials with credentialName param', async () => {
29+
const mockCredentials = [{ id: '1', name: 'My OpenAI Key', credentialName: 'openAIApi' }]
30+
;(mockClient.get as jest.Mock).mockResolvedValue({ data: mockCredentials })
31+
32+
const result = await api.getCredentialsByName('openAIApi')
33+
expect(mockClient.get).toHaveBeenCalledWith('/credentials', { params: { credentialName: 'openAIApi' } })
34+
expect(result).toEqual(mockCredentials)
35+
})
36+
})
37+
})
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type { AxiosInstance } from 'axios'
2+
3+
import type { Credential } from '@/core/types'
4+
5+
/**
6+
* Create credentials API functions bound to a client instance
7+
*/
8+
export function bindCredentialsApi(client: AxiosInstance) {
9+
return {
10+
/**
11+
* Get all credentials
12+
*/
13+
getAllCredentials: async (): Promise<Credential[]> => {
14+
const response = await client.get('/credentials')
15+
return response.data
16+
},
17+
18+
/**
19+
* Get credentials filtered by component credential name
20+
*/
21+
getCredentialsByName: async (credentialName: string): Promise<Credential[]> => {
22+
const response = await client.get('/credentials', { params: { credentialName } })
23+
return response.data
24+
}
25+
}
26+
}
27+
28+
export type CredentialsApi = ReturnType<typeof bindCredentialsApi>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
// API infrastructure - External data layer
22
export { type ChatflowsApi, createChatflowsApi } from './chatflows'
33
export { createApiClient } from './client'
4+
export { bindCredentialsApi, type CredentialsApi } from './credentials'
5+
export { type ApiServices, getLoadMethod, loadMethodRegistry } from './loadMethodRegistry'
6+
export { bindChatModelsApi, type ChatModelsApi } from './models'
47
export { createNodesApi, type NodesApi } from './nodes'
8+
export { bindToolsApi, type ToolsApi } from './tools'
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import type { ApiServices } from './loadMethodRegistry'
2+
import { getLoadMethod, loadMethodRegistry } from './loadMethodRegistry'
3+
4+
const mockApis: ApiServices = {
5+
chatModelsApi: {
6+
getChatModels: jest.fn(),
7+
getModelsByProvider: jest.fn()
8+
},
9+
toolsApi: {
10+
getAllTools: jest.fn()
11+
},
12+
credentialsApi: {
13+
getAllCredentials: jest.fn(),
14+
getCredentialsByName: jest.fn()
15+
}
16+
}
17+
18+
beforeEach(() => {
19+
jest.clearAllMocks()
20+
})
21+
22+
describe('loadMethodRegistry', () => {
23+
describe('listChatModels', () => {
24+
it('should call chatModelsApi.getChatModels()', async () => {
25+
const mockModels = [{ name: 'gpt-4', label: 'GPT-4' }]
26+
;(mockApis.chatModelsApi.getChatModels as jest.Mock).mockResolvedValue(mockModels)
27+
28+
const result = await loadMethodRegistry['listChatModels'](mockApis)
29+
expect(mockApis.chatModelsApi.getChatModels).toHaveBeenCalled()
30+
expect(result).toEqual(mockModels)
31+
})
32+
})
33+
34+
describe('listTools', () => {
35+
it('should call toolsApi.getAllTools()', async () => {
36+
const mockTools = [{ id: '1', name: 'Calculator' }]
37+
;(mockApis.toolsApi.getAllTools as jest.Mock).mockResolvedValue(mockTools)
38+
39+
const result = await loadMethodRegistry['listTools'](mockApis)
40+
expect(mockApis.toolsApi.getAllTools).toHaveBeenCalled()
41+
expect(result).toEqual(mockTools)
42+
})
43+
})
44+
45+
describe('listCredentials', () => {
46+
it('should call credentialsApi.getCredentialsByName() with params.name', async () => {
47+
const mockCredentials = [{ id: '1', name: 'My Key', credentialName: 'openAIApi' }]
48+
;(mockApis.credentialsApi.getCredentialsByName as jest.Mock).mockResolvedValue(mockCredentials)
49+
50+
const result = await loadMethodRegistry['listCredentials'](mockApis, { name: 'openAIApi' })
51+
expect(mockApis.credentialsApi.getCredentialsByName).toHaveBeenCalledWith('openAIApi')
52+
expect(result).toEqual(mockCredentials)
53+
})
54+
})
55+
})
56+
57+
describe('getLoadMethod', () => {
58+
it('should return the registry function for a known key', () => {
59+
const fn = getLoadMethod('listChatModels')
60+
expect(fn).toBeDefined()
61+
expect(typeof fn).toBe('function')
62+
})
63+
64+
it('should return the registry function for listTools', () => {
65+
const fn = getLoadMethod('listTools')
66+
expect(fn).toBeDefined()
67+
expect(typeof fn).toBe('function')
68+
})
69+
70+
it('should return the registry function for listCredentials', () => {
71+
const fn = getLoadMethod('listCredentials')
72+
expect(fn).toBeDefined()
73+
expect(typeof fn).toBe('function')
74+
})
75+
76+
it('should return undefined for an unknown key', () => {
77+
const fn = getLoadMethod('unknownMethod')
78+
expect(fn).toBeUndefined()
79+
})
80+
})
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { CredentialsApi } from './credentials'
2+
import type { ChatModelsApi } from './models'
3+
import type { ToolsApi } from './tools'
4+
5+
export interface ApiServices {
6+
chatModelsApi: ChatModelsApi
7+
toolsApi: ToolsApi
8+
credentialsApi: CredentialsApi
9+
}
10+
11+
/**
12+
* Registry that maps `loadMethod` string keys — as declared on node `InputParam` definitions
13+
* (e.g. `{ loadMethod: 'listTools' }`) — to functions that fetch the corresponding options
14+
* from the Flowise API.
15+
*
16+
* Each entry receives the shared {@link ApiServices} instance and an optional `params` object,
17+
* and must return a `Promise` of the option values to populate the node's dropdown.
18+
*
19+
* ### Built-in entries
20+
* - `listChatModels` — fetches available chat models via `GET /assistants/components/chatmodels`
21+
* - `listTools` — fetches available tool components via `POST /node-load-method/toolAgentflow`
22+
* - `listCredentials` — fetches credentials filtered by `params.name` (credential component name)
23+
* via `GET /credentials?credentialName=<name>`
24+
*
25+
*/
26+
export const loadMethodRegistry: Record<string, (_apis: ApiServices, _params?: Record<string, unknown>) => Promise<unknown>> = {
27+
listChatModels: (apis) => apis.chatModelsApi.getChatModels(),
28+
listTools: (apis) => apis.toolsApi.getAllTools(),
29+
listCredentials: (apis, params) => {
30+
const name = params?.name
31+
if (typeof name !== 'string') {
32+
return Promise.reject(new Error('`listCredentials` requires a string `name` parameter.'))
33+
}
34+
return apis.credentialsApi.getCredentialsByName(name)
35+
}
36+
}
37+
38+
/**
39+
* Looks up a load method handler by its string key.
40+
*
41+
* Returns `undefined` if no handler is registered for the given name,
42+
* which callers should treat as a no-op or fallback.
43+
*
44+
* @param name - The `loadMethod` key declared on a node `InputParam`
45+
*/
46+
export function getLoadMethod(name: string): ((_apis: ApiServices, _params?: Record<string, unknown>) => Promise<unknown>) | undefined {
47+
return loadMethodRegistry[name]
48+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { AxiosInstance } from 'axios'
2+
3+
import { bindChatModelsApi } from './models'
4+
5+
const mockClient = {
6+
get: jest.fn()
7+
} as unknown as jest.Mocked<AxiosInstance>
8+
9+
beforeEach(() => {
10+
jest.clearAllMocks()
11+
})
12+
13+
describe('bindChatModelsApi', () => {
14+
const api = bindChatModelsApi(mockClient)
15+
16+
describe('getChatModels', () => {
17+
it('should call GET /assistants/components/chatmodels', async () => {
18+
const mockModels = [{ name: 'gpt-4', label: 'GPT-4' }]
19+
;(mockClient.get as jest.Mock).mockResolvedValue({ data: mockModels })
20+
21+
const result = await api.getChatModels()
22+
expect(mockClient.get).toHaveBeenCalledWith('/assistants/components/chatmodels')
23+
expect(result).toEqual(mockModels)
24+
})
25+
})
26+
27+
describe('getModelsByProvider', () => {
28+
it('should call GET /assistants/components/chatmodels with provider param', async () => {
29+
const mockModels = [{ name: 'gpt-4', label: 'GPT-4' }]
30+
;(mockClient.get as jest.Mock).mockResolvedValue({ data: mockModels })
31+
32+
const result = await api.getModelsByProvider('openai')
33+
expect(mockClient.get).toHaveBeenCalledWith('/assistants/components/chatmodels', { params: { provider: 'openai' } })
34+
expect(result).toEqual(mockModels)
35+
})
36+
})
37+
})
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type { AxiosInstance } from 'axios'
2+
3+
import type { ChatModel } from '@/core/types'
4+
5+
/**
6+
* Create models API functions bound to a client instance
7+
*/
8+
export function bindChatModelsApi(client: AxiosInstance) {
9+
return {
10+
/**
11+
* Get all available chat models
12+
*/
13+
getChatModels: async (): Promise<ChatModel[]> => {
14+
const response = await client.get('/assistants/components/chatmodels')
15+
return response.data
16+
},
17+
18+
/**
19+
* Get chat models filtered by provider
20+
*/
21+
getModelsByProvider: async (provider: string): Promise<ChatModel[]> => {
22+
const response = await client.get('/assistants/components/chatmodels', { params: { provider } })
23+
return response.data
24+
}
25+
}
26+
}
27+
28+
export type ChatModelsApi = ReturnType<typeof bindChatModelsApi>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { AxiosInstance } from 'axios'
2+
3+
import { bindToolsApi } from './tools'
4+
5+
const mockClient = {
6+
post: jest.fn()
7+
} as unknown as jest.Mocked<AxiosInstance>
8+
9+
beforeEach(() => {
10+
jest.clearAllMocks()
11+
})
12+
13+
describe('bindToolsApi', () => {
14+
const api = bindToolsApi(mockClient)
15+
16+
describe('getAllTools', () => {
17+
it('should POST to /node-load-method/toolAgentflow with listTools loadMethod', async () => {
18+
const mockTools = [{ label: 'Calculator', name: 'calculator' }]
19+
;(mockClient.post as jest.Mock).mockResolvedValue({ data: mockTools })
20+
21+
const result = await api.getAllTools()
22+
expect(mockClient.post).toHaveBeenCalledWith('/node-load-method/toolAgentflow', { loadMethod: 'listTools' })
23+
expect(result).toEqual(mockTools)
24+
})
25+
})
26+
})

0 commit comments

Comments
 (0)