From 5d1b264f0d86865e041e3ac62660c80db7363823 Mon Sep 17 00:00:00 2001 From: Omer Katzir Date: Sun, 7 Sep 2025 22:10:17 +0300 Subject: [PATCH 01/11] agentsSdk module --- package-lock.json | 140 +++++++++++++++++++++++++++++++++++++- package.json | 3 +- src/client.ts | 44 +++++++++--- src/modules/agents.ts | 125 ++++++++++++++++++++++++++++++++++ src/utils/socket-utils.ts | 129 +++++++++++++++++++++++++++++++++++ 5 files changed, 430 insertions(+), 11 deletions(-) create mode 100644 src/modules/agents.ts create mode 100644 src/utils/socket-utils.ts diff --git a/package-lock.json b/package-lock.json index 736ccfa..fd67473 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "0.7.0", "license": "MIT", "dependencies": { - "axios": "^1.6.2" + "axios": "^1.6.2", + "socket.io-client": "^4.7.5" }, "devDependencies": { "@vitest/coverage-istanbul": "^1.0.0", @@ -1235,6 +1236,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1981,6 +1988,45 @@ "dev": true, "license": "ISC" }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -3130,7 +3176,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -3601,6 +3646,68 @@ "node": ">= 10" } }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -4261,6 +4368,35 @@ "dev": true, "license": "ISC" }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index eb09e84..831524a 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "prepublishOnly": "npm run build" }, "dependencies": { - "axios": "^1.6.2" + "axios": "^1.6.2", + "socket.io-client": "^4.7.5" }, "devDependencies": { "vitest": "^1.0.0", diff --git a/src/client.ts b/src/client.ts index a4e8049..6f9612b 100644 --- a/src/client.ts +++ b/src/client.ts @@ -5,6 +5,7 @@ import { createAuthModule } from "./modules/auth.js"; import { createSsoModule } from "./modules/sso.js"; import { getAccessToken } from "./utils/auth-utils.js"; import { createFunctionsModule } from "./modules/functions.js"; +import { createAgentsModule } from "./modules/agents.js"; /** * Create a Base44 client instance @@ -80,6 +81,11 @@ export function createClient(config: { integrations: createIntegrationsModule(axiosClient, appId), auth: createAuthModule(axiosClient, functionsAxiosClient, appId), functions: createFunctionsModule(functionsAxiosClient, appId), + agents: createAgentsModule({ + serverUrl, + appId, + token, + }), }; const serviceRoleModules = { @@ -87,6 +93,11 @@ export function createClient(config: { integrations: createIntegrationsModule(serviceRoleAxiosClient, appId), sso: createSsoModule(serviceRoleAxiosClient, appId, token), functions: createFunctionsModule(serviceRoleFunctionsAxiosClient, appId), + agents: createAgentsModule({ + serverUrl, + appId, + token: serviceToken, + }), }; // Always try to get token from localStorage or URL parameters @@ -95,6 +106,9 @@ export function createClient(config: { const accessToken = token || getAccessToken(); if (accessToken) { userModules.auth.setToken(accessToken); + userModules.agents.updateConfig({ + token: accessToken, + }); } } @@ -144,10 +158,12 @@ export function createClient(config: { */ get asServiceRole() { if (!serviceToken) { - throw new Error('Service token is required to use asServiceRole. Please provide a serviceToken when creating the client.'); + throw new Error( + "Service token is required to use asServiceRole. Please provide a serviceToken when creating the client." + ); } return serviceRoleModules; - } + }, }; return client; @@ -172,17 +188,29 @@ export function createClientFromRequest(request: Request) { let userToken: string | undefined; if (serviceRoleAuthHeader !== null) { - if (serviceRoleAuthHeader === '' || !serviceRoleAuthHeader.startsWith('Bearer ') || serviceRoleAuthHeader.split(' ').length !== 2) { - throw new Error('Invalid authorization header format. Expected "Bearer "'); + if ( + serviceRoleAuthHeader === "" || + !serviceRoleAuthHeader.startsWith("Bearer ") || + serviceRoleAuthHeader.split(" ").length !== 2 + ) { + throw new Error( + 'Invalid authorization header format. Expected "Bearer "' + ); } - serviceRoleToken = serviceRoleAuthHeader.split(' ')[1]; + serviceRoleToken = serviceRoleAuthHeader.split(" ")[1]; } if (authHeader !== null) { - if (authHeader === '' || !authHeader.startsWith('Bearer ') || authHeader.split(' ').length !== 2) { - throw new Error('Invalid authorization header format. Expected "Bearer "'); + if ( + authHeader === "" || + !authHeader.startsWith("Bearer ") || + authHeader.split(" ").length !== 2 + ) { + throw new Error( + 'Invalid authorization header format. Expected "Bearer "' + ); } - userToken = authHeader.split(' ')[1]; + userToken = authHeader.split(" ")[1]; } return createClient({ diff --git a/src/modules/agents.ts b/src/modules/agents.ts new file mode 100644 index 0000000..4d7ea3d --- /dev/null +++ b/src/modules/agents.ts @@ -0,0 +1,125 @@ +import { AxiosInstance } from "axios"; +import { RoomsSocket, RoomsSocketConfig } from "../utils/socket-utils"; +import { createAxiosClient } from "../utils/axios-client"; + +export type AgentsModuleConfig = { + serverUrl: string; + appId: string; + token?: string; +}; + +export function createAgentsModule({ + appId, + serverUrl, + token, +}: AgentsModuleConfig) { + let currentConversation: any = null; + const socketConfig: RoomsSocketConfig = { + serverUrl, + mountPath: "/ws-user-apps/socket.io/", + transports: ["websocket"], + query: { + appId, + token, + }, + }; + + const axiosConfig: AgentsModuleConfig = { + serverUrl, + appId, + token, + }; + + let axios = createAgentsAxiosClient({ + serverUrl, + appId, + token, + }); + + const roomSocket = RoomsSocket({ + config: socketConfig, + }); + + const updateConfig = (config: Partial) => { + axios = createAgentsAxiosClient({ ...axiosConfig, ...config }); + roomSocket.updateConfig({ ...socketConfig, ...config }); + }; + + const getConversations = () => { + return axios.get(`/conversations`); + }; + + const getConversation = (conversationId: string) => { + return axios.get(`/conversations/${conversationId}`); + }; + + const listConversations = (filterParams: any) => { + return axios.get(`/conversations`, { params: filterParams }); + }; + + const createConversation = (conversation: any) => { + return axios.post(`/conversations`, conversation); + }; + + const addMessage = (conversation: any, message: any) => { + // this whole trick with current conversation so that we can call the onUpdateModel with the latest messages + let convLatestMessages = null; + if (currentConversation && currentConversation.id === conversation.id) { + convLatestMessages = currentConversation.messages; + } else { + currentConversation = conversation; + convLatestMessages = conversation.messages; + } + conversation.messages = [...convLatestMessages, message]; + roomSocket.handlers.update_model({ + room: `/agent-conversations/${conversation.id}`, + data: JSON.stringify(conversation), + }); + return axios.post(`/conversations/${conversation.id}/messages`, message); + }; + + const subscribeToConversation = (conversationId: string, onUpdate: any) => { + return roomSocket.subscribeToRoom( + `/agent-conversations/${conversationId}`, + { + connect: () => {}, + update_model: ({ data: jsonStr }) => { + const data = JSON.parse(jsonStr) as {} & { id: string }; + if (currentConversation && currentConversation.id === data.id) { + currentConversation = data; + } + onUpdate(data); + }, + } + ); + }; + + return { + getConversations, + getConversation, + listConversations, + createConversation, + addMessage, + subscribeToConversation, + updateConfig, + }; +} + +function createAgentsAxiosClient({ + serverUrl, + appId, + token, +}: AgentsModuleConfig) { + const axios = createAxiosClient({ + baseURL: `${serverUrl}/api/apps/${appId}/agents`, + appId, + serverUrl, + token, + interceptResponses: false, + headers: { + "X-App-Id": String(appId), + }, + }); + + return axios; +} diff --git a/src/utils/socket-utils.ts b/src/utils/socket-utils.ts new file mode 100644 index 0000000..0e7f2d6 --- /dev/null +++ b/src/utils/socket-utils.ts @@ -0,0 +1,129 @@ +import { Socket, io } from "socket.io-client"; + +export type RoomsSocketConfig = { + serverUrl: string; + mountPath: string; + transports: string[]; + query: { appId: string; token?: string }; +}; + +export type TSocketRoom = string; +export type TJsonStr = string; + +type RoomsSocketEventsMap = { + listen: { + connect: () => void; + update_model: (msg: { room: string; data: TJsonStr; }) => void; + }; + emit: { + join: (room: string) => void; + leave: (room: string) => void; + }; +}; + +type TEvent = keyof RoomsSocketEventsMap["listen"]; + +type THandler = ( + ...args: Parameters +) => void; + +function initializeSocket( + config: RoomsSocketConfig, + handlers: { + [k in TEvent]: ( + ...args: Parameters + ) => void; + } +) { + const socket = io(config.serverUrl, { + path: config.mountPath, + transports: config.transports, + query: config.query, + }) as Socket; + + socket.on("connect", () => { + console.log("connect", socket.id); + handlers.connect(); + }); + + socket.on("update_model", (msg) => { + handlers.update_model(msg); + }); + + return socket; +} + +export function RoomsSocket({ config }: { config: RoomsSocketConfig }) { + const roomsToListeners: Record< + TSocketRoom, + { [k in TEvent]: THandler }[] + > = {}; + + const handlers: { [k in TEvent]: THandler } = { + connect: () => { + Object.keys(roomsToListeners).forEach((room) => { + joinRoom(room); + getListeners(room)?.forEach(({ connect: connectHandler }) => { + connectHandler(); + }); + }); + }, + update_model: (msg) => { + if (roomsToListeners[msg.room]) { + getListeners(msg.room)?.forEach(({ update_model }) => { + update_model(msg); + }); + } + }, + }; + + let socket = initializeSocket(config, handlers); + + function cleanup() { + if (socket) { + socket.disconnect(); + } + } + + function updateConfig(config: RoomsSocketConfig) { + cleanup(); + socket = initializeSocket(config, handlers); + } + + function joinRoom(room: string) { + socket.emit("join", room); + } + + function leaveRoom(room: string) { + socket.emit("leave", room); + } + + function getListeners(room: string) { + return roomsToListeners[room]; + } + + const subscribeToRoom = ( + room: TSocketRoom, + handlers: { [k in TEvent]: THandler } + ) => { + if (!roomsToListeners[room]) { + joinRoom(room); + roomsToListeners[room] = []; + } + + roomsToListeners[room].push(handlers); + + return () => { + roomsToListeners[room] = roomsToListeners[room].filter( + (listener) => listener !== handlers + ); + }; + }; + + return { + socket: Object.freeze(socket), + subscribeToRoom, + updateConfig, + handlers, + }; +} From 704d95653eb2d05bba3e0b0d36604229e83aa1c3 Mon Sep 17 00:00:00 2001 From: Omer Katzir Date: Sun, 7 Sep 2025 22:56:58 +0300 Subject: [PATCH 02/11] response types --- src/modules/agents.ts | 51 +++++++++++++++++++++---------------- src/modules/agents.types.ts | 48 ++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 22 deletions(-) create mode 100644 src/modules/agents.types.ts diff --git a/src/modules/agents.ts b/src/modules/agents.ts index 4d7ea3d..9af8d3f 100644 --- a/src/modules/agents.ts +++ b/src/modules/agents.ts @@ -1,6 +1,6 @@ -import { AxiosInstance } from "axios"; import { RoomsSocket, RoomsSocketConfig } from "../utils/socket-utils"; import { createAxiosClient } from "../utils/axios-client"; +import { AgentConversation, AgentMessage } from "./agents.types"; export type AgentsModuleConfig = { serverUrl: string; @@ -10,11 +10,11 @@ export type AgentsModuleConfig = { export function createAgentsModule({ appId, - serverUrl, - token, + serverUrl, + token, }: AgentsModuleConfig) { let currentConversation: any = null; - const socketConfig: RoomsSocketConfig = { + const socketConfig: RoomsSocketConfig = { serverUrl, mountPath: "/ws-user-apps/socket.io/", transports: ["websocket"], @@ -24,18 +24,18 @@ export function createAgentsModule({ }, }; - const axiosConfig: AgentsModuleConfig = { - serverUrl, - appId, - token, - }; + const axiosConfig: AgentsModuleConfig = { + serverUrl, + appId, + token, + }; + + let axios = createAgentsAxiosClient({ + serverUrl, + appId, + token, + }); - let axios = createAgentsAxiosClient({ - serverUrl, - appId, - token, - }); - const roomSocket = RoomsSocket({ config: socketConfig, }); @@ -46,19 +46,23 @@ export function createAgentsModule({ }; const getConversations = () => { - return axios.get(`/conversations`); + return axios.get(`/conversations`); }; const getConversation = (conversationId: string) => { - return axios.get(`/conversations/${conversationId}`); + return axios.get( + `/conversations/${conversationId}` + ); }; const listConversations = (filterParams: any) => { - return axios.get(`/conversations`, { params: filterParams }); + return axios.get(`/conversations`, { + params: filterParams, + }); }; const createConversation = (conversation: any) => { - return axios.post(`/conversations`, conversation); + return axios.post(`/conversations`, conversation); }; const addMessage = (conversation: any, message: any) => { @@ -75,7 +79,10 @@ export function createAgentsModule({ room: `/agent-conversations/${conversation.id}`, data: JSON.stringify(conversation), }); - return axios.post(`/conversations/${conversation.id}/messages`, message); + return axios.post( + `/conversations/${conversation.id}/messages`, + message + ); }; const subscribeToConversation = (conversationId: string, onUpdate: any) => { @@ -115,8 +122,8 @@ function createAgentsAxiosClient({ appId, serverUrl, token, - interceptResponses: false, - headers: { + interceptResponses: true, + headers: { "X-App-Id": String(appId), }, }); diff --git a/src/modules/agents.types.ts b/src/modules/agents.types.ts new file mode 100644 index 0000000..c6c0807 --- /dev/null +++ b/src/modules/agents.types.ts @@ -0,0 +1,48 @@ + + + + +export type AgentConversation = { + id: string; + app_id: string; + agent_name: string; + created_by_id: string; + messages: AgentMessage[]; + metadata?: Record; +}; + + +export type AgentMessage = { + id: string; + role: "user" | "assistant" | "system"; + reasoning: { + start_date: string; + end_date?: string; + content: string; + } + content?: string | Record | null; + file_urls?: string[] | null; + tool_calls?: {id: string; + name: string; + arguments_string: string; + status: "running" | "success" | "error" | "stopped"; + results?: string | null; + }[] | null; + + usage: { prompt_tokens?: number + completion_tokens?: number + } | null; + hidden?: boolean; + custom_context?: {message: string; + data: Record; + type: string; + }[] | null; + model: string | null; + checkpoint_id: string | null; + metadata?: { + created_date: string; + created_by_email: string; + created_by_full_name: string | null; + }; + additional_message_params?: Record; +}; From 3e548f7538a27e5a52e40d3f3dd99a14cbf8b51e Mon Sep 17 00:00:00 2001 From: Omer Katzir Date: Mon, 8 Sep 2025 17:25:00 +0300 Subject: [PATCH 03/11] extract socket and axios client instantiations --- src/client.ts | 35 +++++++++++-- src/modules/agents.ts | 104 ++++++++++---------------------------- src/modules/auth.ts | 1 + src/utils/axios-client.ts | 4 ++ src/utils/socket-utils.ts | 28 +++++++--- 5 files changed, 84 insertions(+), 88 deletions(-) diff --git a/src/client.ts b/src/client.ts index 6f9612b..cf74ee0 100644 --- a/src/client.ts +++ b/src/client.ts @@ -6,6 +6,11 @@ import { createSsoModule } from "./modules/sso.js"; import { getAccessToken } from "./utils/auth-utils.js"; import { createFunctionsModule } from "./modules/functions.js"; import { createAgentsModule } from "./modules/agents.js"; +import { RoomsSocket, RoomsSocketConfig } from "./utils/socket-utils.js"; + +export type CreateClientOptions = { + onError?: (error: Error) => void; +}; /** * Create a Base44 client instance @@ -23,6 +28,7 @@ export function createClient(config: { token?: string; serviceToken?: string; requiresAuth?: boolean; + options?: CreateClientOptions; }) { const { serverUrl = "https://base44.app", @@ -30,8 +36,21 @@ export function createClient(config: { token, serviceToken, requiresAuth = false, + options, } = config; + const socketConfig: RoomsSocketConfig = { + serverUrl, + mountPath: "/ws-user-apps/socket.io/", + transports: ["websocket"], + appId, + token, + }; + + const socket = RoomsSocket({ + config: socketConfig, + }); + const axiosClient = createAxiosClient({ baseURL: `${serverUrl}/api`, headers: { @@ -41,6 +60,7 @@ export function createClient(config: { requiresAuth, appId, serverUrl, + onError: options?.onError, }); const functionsAxiosClient = createAxiosClient({ @@ -53,6 +73,7 @@ export function createClient(config: { appId, serverUrl, interceptResponses: false, + onError: options?.onError, }); const serviceRoleAxiosClient = createAxiosClient({ @@ -63,6 +84,7 @@ export function createClient(config: { token: serviceToken, serverUrl, appId, + onError: options?.onError, }); const serviceRoleFunctionsAxiosClient = createAxiosClient({ @@ -82,9 +104,9 @@ export function createClient(config: { auth: createAuthModule(axiosClient, functionsAxiosClient, appId), functions: createFunctionsModule(functionsAxiosClient, appId), agents: createAgentsModule({ - serverUrl, + axios: axiosClient, + socket, appId, - token, }), }; @@ -94,9 +116,9 @@ export function createClient(config: { sso: createSsoModule(serviceRoleAxiosClient, appId, token), functions: createFunctionsModule(serviceRoleFunctionsAxiosClient, appId), agents: createAgentsModule({ - serverUrl, + axios: serviceRoleAxiosClient, + socket, appId, - token: serviceToken, }), }; @@ -106,7 +128,7 @@ export function createClient(config: { const accessToken = token || getAccessToken(); if (accessToken) { userModules.auth.setToken(accessToken); - userModules.agents.updateConfig({ + socket.updateConfig({ token: accessToken, }); } @@ -138,6 +160,9 @@ export function createClient(config: { */ setToken(newToken: string) { userModules.auth.setToken(newToken); + socket.updateConfig({ + token: newToken, + }); }, /** diff --git a/src/modules/agents.ts b/src/modules/agents.ts index 9af8d3f..9689b0c 100644 --- a/src/modules/agents.ts +++ b/src/modules/agents.ts @@ -1,68 +1,43 @@ -import { RoomsSocket, RoomsSocketConfig } from "../utils/socket-utils"; -import { createAxiosClient } from "../utils/axios-client"; -import { AgentConversation, AgentMessage } from "./agents.types"; +import { RoomsSocket, RoomsSocketConfig } from "../utils/socket-utils.js"; +import { createAxiosClient } from "../utils/axios-client.js"; +import { AgentConversation, AgentMessage } from "./agents.types.js"; +import { AxiosInstance } from "axios"; export type AgentsModuleConfig = { - serverUrl: string; + axios: AxiosInstance; + socket: ReturnType; appId: string; - token?: string; }; export function createAgentsModule({ + axios, + socket, appId, - serverUrl, - token, }: AgentsModuleConfig) { let currentConversation: any = null; - const socketConfig: RoomsSocketConfig = { - serverUrl, - mountPath: "/ws-user-apps/socket.io/", - transports: ["websocket"], - query: { - appId, - token, - }, - }; - - const axiosConfig: AgentsModuleConfig = { - serverUrl, - appId, - token, - }; - - let axios = createAgentsAxiosClient({ - serverUrl, - appId, - token, - }); - - const roomSocket = RoomsSocket({ - config: socketConfig, - }); - - const updateConfig = (config: Partial) => { - axios = createAgentsAxiosClient({ ...axiosConfig, ...config }); - roomSocket.updateConfig({ ...socketConfig, ...config }); - }; + const baseURL = `/apps/${appId}/agents`; const getConversations = () => { - return axios.get(`/conversations`); + return axios.get(`${baseURL}/conversations`); }; const getConversation = (conversationId: string) => { return axios.get( - `/conversations/${conversationId}` + `${baseURL}/conversations/${conversationId}` ); }; const listConversations = (filterParams: any) => { - return axios.get(`/conversations`, { + return axios.get(`${baseURL}/conversations`, { params: filterParams, }); }; const createConversation = (conversation: any) => { - return axios.post(`/conversations`, conversation); + return axios.post( + `${baseURL}/conversations`, + conversation + ); }; const addMessage = (conversation: any, message: any) => { @@ -75,30 +50,27 @@ export function createAgentsModule({ convLatestMessages = conversation.messages; } conversation.messages = [...convLatestMessages, message]; - roomSocket.handlers.update_model({ + socket.handlers.update_model({ room: `/agent-conversations/${conversation.id}`, data: JSON.stringify(conversation), }); return axios.post( - `/conversations/${conversation.id}/messages`, + `${baseURL}/conversations/${conversation.id}/messages`, message ); }; const subscribeToConversation = (conversationId: string, onUpdate: any) => { - return roomSocket.subscribeToRoom( - `/agent-conversations/${conversationId}`, - { - connect: () => {}, - update_model: ({ data: jsonStr }) => { - const data = JSON.parse(jsonStr) as {} & { id: string }; - if (currentConversation && currentConversation.id === data.id) { - currentConversation = data; - } - onUpdate(data); - }, - } - ); + return socket.subscribeToRoom(`/agent-conversations/${conversationId}`, { + connect: () => {}, + update_model: ({ data: jsonStr }) => { + const data = JSON.parse(jsonStr) as {} & { id: string }; + if (currentConversation && currentConversation.id === data.id) { + currentConversation = data; + } + onUpdate(data); + }, + }); }; return { @@ -108,25 +80,5 @@ export function createAgentsModule({ createConversation, addMessage, subscribeToConversation, - updateConfig, }; } - -function createAgentsAxiosClient({ - serverUrl, - appId, - token, -}: AgentsModuleConfig) { - const axios = createAxiosClient({ - baseURL: `${serverUrl}/api/apps/${appId}/agents`, - appId, - serverUrl, - token, - interceptResponses: true, - headers: { - "X-App-Id": String(appId), - }, - }); - - return axios; -} diff --git a/src/modules/auth.ts b/src/modules/auth.ts index d409c4b..1927a20 100644 --- a/src/modules/auth.ts +++ b/src/modules/auth.ts @@ -91,6 +91,7 @@ export function createAuthModule( setToken(token: string, saveToStorage = true) { if (!token) return; + // handle token change for axios clients axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; functionsAxiosClient.defaults.headers.common[ "Authorization" diff --git a/src/utils/axios-client.ts b/src/utils/axios-client.ts index d1f7ff9..f984901 100644 --- a/src/utils/axios-client.ts +++ b/src/utils/axios-client.ts @@ -89,6 +89,7 @@ export function createAxiosClient({ appId, serverUrl, interceptResponses = true, + onError, }: { baseURL: string; headers?: Record; @@ -97,6 +98,7 @@ export function createAxiosClient({ appId: string; serverUrl: string; interceptResponses?: boolean; + onError?: (error: Error) => void; }) { const client = axios.create({ baseURL, @@ -161,6 +163,8 @@ export function createAxiosClient({ }, 100); } + onError?.(base44Error); + return Promise.reject(base44Error); } ); diff --git a/src/utils/socket-utils.ts b/src/utils/socket-utils.ts index 0e7f2d6..fdf0c3e 100644 --- a/src/utils/socket-utils.ts +++ b/src/utils/socket-utils.ts @@ -4,7 +4,8 @@ export type RoomsSocketConfig = { serverUrl: string; mountPath: string; transports: string[]; - query: { appId: string; token?: string }; + appId: string; + token?: string; }; export type TSocketRoom = string; @@ -13,7 +14,7 @@ export type TJsonStr = string; type RoomsSocketEventsMap = { listen: { connect: () => void; - update_model: (msg: { room: string; data: TJsonStr; }) => void; + update_model: (msg: { room: string; data: TJsonStr }) => void; }; emit: { join: (room: string) => void; @@ -38,7 +39,10 @@ function initializeSocket( const socket = io(config.serverUrl, { path: config.mountPath, transports: config.transports, - query: config.query, + query: { + appId: config.appId, + token: config.token, + }, }) as Socket; socket.on("connect", () => { @@ -54,6 +58,7 @@ function initializeSocket( } export function RoomsSocket({ config }: { config: RoomsSocketConfig }) { + let currentConfig = { ...config }; const roomsToListeners: Record< TSocketRoom, { [k in TEvent]: THandler }[] @@ -80,14 +85,22 @@ export function RoomsSocket({ config }: { config: RoomsSocketConfig }) { let socket = initializeSocket(config, handlers); function cleanup() { + disconnect(); + } + + function disconnect() { if (socket) { socket.disconnect(); } } - function updateConfig(config: RoomsSocketConfig) { + function updateConfig(config: Partial) { cleanup(); - socket = initializeSocket(config, handlers); + currentConfig = { + ...currentConfig, + ...config, + }; + socket = initializeSocket(currentConfig, handlers); } function joinRoom(room: string) { @@ -121,9 +134,10 @@ export function RoomsSocket({ config }: { config: RoomsSocketConfig }) { }; return { - socket: Object.freeze(socket), + socket, subscribeToRoom, updateConfig, - handlers, + handlers, + disconnect, }; } From a16f3f7d2769a87626f6d7aa7183bff58c6cada2 Mon Sep 17 00:00:00 2001 From: Omer Katzir Date: Mon, 8 Sep 2025 18:57:42 +0300 Subject: [PATCH 04/11] fix query --- src/client.ts | 9 ++++++--- src/utils/socket-utils.ts | 35 +++++++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/client.ts b/src/client.ts index cf74ee0..ffac818 100644 --- a/src/client.ts +++ b/src/client.ts @@ -108,6 +108,9 @@ export function createClient(config: { socket, appId, }), + cleanup: () => { + socket.disconnect(); + }, }; const serviceRoleModules = { @@ -120,6 +123,9 @@ export function createClient(config: { socket, appId, }), + cleanup: () => { + socket.disconnect(); + }, }; // Always try to get token from localStorage or URL parameters @@ -128,9 +134,6 @@ export function createClient(config: { const accessToken = token || getAccessToken(); if (accessToken) { userModules.auth.setToken(accessToken); - socket.updateConfig({ - token: accessToken, - }); } } diff --git a/src/utils/socket-utils.ts b/src/utils/socket-utils.ts index fdf0c3e..5758c5a 100644 --- a/src/utils/socket-utils.ts +++ b/src/utils/socket-utils.ts @@ -1,4 +1,5 @@ import { Socket, io } from "socket.io-client"; +import { getAccessToken } from "./auth-utils.js"; export type RoomsSocketConfig = { serverUrl: string; @@ -15,6 +16,7 @@ type RoomsSocketEventsMap = { listen: { connect: () => void; update_model: (msg: { room: string; data: TJsonStr }) => void; + error: (error: Error) => void; }; emit: { join: (room: string) => void; @@ -30,28 +32,37 @@ type THandler = ( function initializeSocket( config: RoomsSocketConfig, - handlers: { + handlers: Partial<{ [k in TEvent]: ( ...args: Parameters ) => void; - } + }> ) { const socket = io(config.serverUrl, { path: config.mountPath, transports: config.transports, query: { - appId: config.appId, - token: config.token, + app_id: config.appId, + token: config.token ?? getAccessToken(), }, }) as Socket; socket.on("connect", () => { console.log("connect", socket.id); - handlers.connect(); + handlers.connect?.(); }); socket.on("update_model", (msg) => { - handlers.update_model(msg); + handlers.update_model?.(msg); + }); + + socket.on("error", (error) => { + handlers.error?.(error); + }); + + socket.on("connect_error", (error) => { + console.error("connect_error", error); + handlers.error?.(error); }); return socket; @@ -61,7 +72,7 @@ export function RoomsSocket({ config }: { config: RoomsSocketConfig }) { let currentConfig = { ...config }; const roomsToListeners: Record< TSocketRoom, - { [k in TEvent]: THandler }[] + Partial<{ [k in TEvent]: THandler }>[] > = {}; const handlers: { [k in TEvent]: THandler } = { @@ -69,17 +80,21 @@ export function RoomsSocket({ config }: { config: RoomsSocketConfig }) { Object.keys(roomsToListeners).forEach((room) => { joinRoom(room); getListeners(room)?.forEach(({ connect: connectHandler }) => { - connectHandler(); + connectHandler?.(); }); }); }, update_model: (msg) => { if (roomsToListeners[msg.room]) { getListeners(msg.room)?.forEach(({ update_model }) => { - update_model(msg); + update_model?.(msg); }); } }, + error: (error) => { + console.error("error", error); + handlers.error?.(error); + }, }; let socket = initializeSocket(config, handlers); @@ -117,7 +132,7 @@ export function RoomsSocket({ config }: { config: RoomsSocketConfig }) { const subscribeToRoom = ( room: TSocketRoom, - handlers: { [k in TEvent]: THandler } + handlers: Partial<{ [k in TEvent]: THandler }> ) => { if (!roomsToListeners[room]) { joinRoom(room); From 9002682ef5b210f5fbf14734128858ec0cc80f15 Mon Sep 17 00:00:00 2001 From: Omer Katzir Date: Tue, 9 Sep 2025 21:38:49 +0300 Subject: [PATCH 05/11] async listners --- src/client.ts | 2 ++ src/index.ts | 6 +++- src/modules/agents.ts | 57 +++++++++++++++++++------------------ src/modules/agents.types.ts | 48 ------------------------------- src/modules/types.ts | 43 ++++++++++++++++++++++++++++ src/types.ts | 12 ++++++++ src/utils/socket-utils.ts | 48 +++++++++++++++++++------------ 7 files changed, 121 insertions(+), 95 deletions(-) delete mode 100644 src/modules/agents.types.ts create mode 100644 src/modules/types.ts create mode 100644 src/types.ts diff --git a/src/client.ts b/src/client.ts index ffac818..45b6299 100644 --- a/src/client.ts +++ b/src/client.ts @@ -12,6 +12,8 @@ export type CreateClientOptions = { onError?: (error: Error) => void; }; +export type Base44SDK = ReturnType; + /** * Create a Base44 client instance * @param {Object} config - Client configuration diff --git a/src/index.ts b/src/index.ts index 2002286..871e699 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import { createClient, createClientFromRequest } from "./client.js"; +import { createClient, createClientFromRequest, Base44SDK } from "./client.js"; import { Base44Error } from "./utils/axios-client.js"; import { getAccessToken, @@ -16,3 +16,7 @@ export { removeAccessToken, getLoginUrl, }; + +export type { Base44SDK }; + +export * from "./types.js"; diff --git a/src/modules/agents.ts b/src/modules/agents.ts index 9689b0c..4440486 100644 --- a/src/modules/agents.ts +++ b/src/modules/agents.ts @@ -1,7 +1,7 @@ -import { RoomsSocket, RoomsSocketConfig } from "../utils/socket-utils.js"; -import { createAxiosClient } from "../utils/axios-client.js"; -import { AgentConversation, AgentMessage } from "./agents.types.js"; +import { RoomsSocket } from "../utils/socket-utils.js"; +import { AgentConversation, AgentMessage } from "./types.js"; import { AxiosInstance } from "axios"; +import { ModelFilterParams } from "../types.js"; export type AgentsModuleConfig = { axios: AxiosInstance; @@ -14,7 +14,6 @@ export function createAgentsModule({ socket, appId, }: AgentsModuleConfig) { - let currentConversation: any = null; const baseURL = `/apps/${appId}/agents`; const getConversations = () => { @@ -27,48 +26,50 @@ export function createAgentsModule({ ); }; - const listConversations = (filterParams: any) => { + const listConversations = (filterParams: ModelFilterParams) => { return axios.get(`${baseURL}/conversations`, { params: filterParams, }); }; - const createConversation = (conversation: any) => { + const createConversation = (conversation: { + agent_name: string; + metadata?: Record; + }) => { return axios.post( `${baseURL}/conversations`, conversation ); }; - - const addMessage = (conversation: any, message: any) => { - // this whole trick with current conversation so that we can call the onUpdateModel with the latest messages - let convLatestMessages = null; - if (currentConversation && currentConversation.id === conversation.id) { - convLatestMessages = currentConversation.messages; - } else { - currentConversation = conversation; - convLatestMessages = conversation.messages; - } - conversation.messages = [...convLatestMessages, message]; - socket.handlers.update_model({ - room: `/agent-conversations/${conversation.id}`, - data: JSON.stringify(conversation), - }); + + const addMessage = async ( + conversation: AgentConversation, + message: AgentMessage + ) => { + const room = `/agent-conversations/${conversation.id}`; + await socket.updateModel( + room, + { + ...conversation, + messages: [...(conversation.messages || []), message], + } + ); return axios.post( `${baseURL}/conversations/${conversation.id}/messages`, message ); }; - const subscribeToConversation = (conversationId: string, onUpdate: any) => { - return socket.subscribeToRoom(`/agent-conversations/${conversationId}`, { + const subscribeToConversation = ( + conversationId: string, + onUpdate?: (conversation: AgentConversation) => void + ) => { + const room = `/agent-conversations/${conversationId}`; + return socket.subscribeToRoom(room, { connect: () => {}, update_model: ({ data: jsonStr }) => { - const data = JSON.parse(jsonStr) as {} & { id: string }; - if (currentConversation && currentConversation.id === data.id) { - currentConversation = data; - } - onUpdate(data); + const conv = JSON.parse(jsonStr) as AgentConversation; + onUpdate?.(conv); }, }); }; diff --git a/src/modules/agents.types.ts b/src/modules/agents.types.ts deleted file mode 100644 index c6c0807..0000000 --- a/src/modules/agents.types.ts +++ /dev/null @@ -1,48 +0,0 @@ - - - - -export type AgentConversation = { - id: string; - app_id: string; - agent_name: string; - created_by_id: string; - messages: AgentMessage[]; - metadata?: Record; -}; - - -export type AgentMessage = { - id: string; - role: "user" | "assistant" | "system"; - reasoning: { - start_date: string; - end_date?: string; - content: string; - } - content?: string | Record | null; - file_urls?: string[] | null; - tool_calls?: {id: string; - name: string; - arguments_string: string; - status: "running" | "success" | "error" | "stopped"; - results?: string | null; - }[] | null; - - usage: { prompt_tokens?: number - completion_tokens?: number - } | null; - hidden?: boolean; - custom_context?: {message: string; - data: Record; - type: string; - }[] | null; - model: string | null; - checkpoint_id: string | null; - metadata?: { - created_date: string; - created_by_email: string; - created_by_full_name: string | null; - }; - additional_message_params?: Record; -}; diff --git a/src/modules/types.ts b/src/modules/types.ts new file mode 100644 index 0000000..a6c1337 --- /dev/null +++ b/src/modules/types.ts @@ -0,0 +1,43 @@ +export type AgentConversation = { + id: string; + app_id: string; + agent_name: string; + created_by_id: string; + messages: AgentMessage[]; + metadata?: Record; +}; + +export type AgentMessage = { + id: string; + role: "user" | "assistant" | "system"; + reasoning?: { + start_date: string; + end_date?: string; + content: string; + }; + content?: string | Record | null; + file_urls?: string[] | null; + tool_calls?: + | { + id: string; + name: string; + arguments_string: string; + status: "running" | "success" | "error" | "stopped"; + results?: string | null; + }[] + | null; + + usage?: { prompt_tokens?: number; completion_tokens?: number } | null; + hidden?: boolean; + custom_context?: + | { message: string; data: Record; type: string }[] + | null; + model?: string | null; + checkpoint_id?: string | null; + metadata?: { + created_date: string; + created_by_email: string; + created_by_full_name: string | null; + }; + additional_message_params?: Record; +}; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..8580f1e --- /dev/null +++ b/src/types.ts @@ -0,0 +1,12 @@ +export * from "./modules/types.js"; + +export type ModelFilterParams = { + q?: Record; + sort?: string | null; + sort_by?: string | null; + limit?: number | null; + skip?: number | null; + fields?: string[] | null; +}; + + diff --git a/src/utils/socket-utils.ts b/src/utils/socket-utils.ts index 5758c5a..ea9425c 100644 --- a/src/utils/socket-utils.ts +++ b/src/utils/socket-utils.ts @@ -14,9 +14,12 @@ export type TJsonStr = string; type RoomsSocketEventsMap = { listen: { - connect: () => void; - update_model: (msg: { room: string; data: TJsonStr }) => void; - error: (error: Error) => void; + connect: () => Promise | void; + update_model: (msg: { + room: string; + data: TJsonStr; + }) => Promise | void; + error: (error: Error) => Promise | void; }; emit: { join: (room: string) => void; @@ -26,9 +29,7 @@ type RoomsSocketEventsMap = { type TEvent = keyof RoomsSocketEventsMap["listen"]; -type THandler = ( - ...args: Parameters -) => void; +type THandler = RoomsSocketEventsMap["listen"][E]; function initializeSocket( config: RoomsSocketConfig, @@ -68,6 +69,8 @@ function initializeSocket( return socket; } +export type RoomsSocket = ReturnType; + export function RoomsSocket({ config }: { config: RoomsSocketConfig }) { let currentConfig = { ...config }; const roomsToListeners: Record< @@ -76,24 +79,28 @@ export function RoomsSocket({ config }: { config: RoomsSocketConfig }) { > = {}; const handlers: { [k in TEvent]: THandler } = { - connect: () => { + connect: async () => { + const promises: Promise[] = []; Object.keys(roomsToListeners).forEach((room) => { joinRoom(room); - getListeners(room)?.forEach(({ connect: connectHandler }) => { - connectHandler?.(); + const listeners = getListeners(room); + listeners?.forEach(({ connect }) => { + const promise = async () => connect?.(); + promises.push(promise()); }); }); + await Promise.all(promises); }, - update_model: (msg) => { - if (roomsToListeners[msg.room]) { - getListeners(msg.room)?.forEach(({ update_model }) => { - update_model?.(msg); - }); - } + update_model: async (msg) => { + const listeners = getListeners(msg.room); + const promises = listeners.map((listener) => + listener.update_model?.(msg) + ); + await Promise.all(promises); }, - error: (error) => { + error: async (error) => { console.error("error", error); - handlers.error?.(error); + await handlers.error?.(error); }, }; @@ -126,6 +133,11 @@ export function RoomsSocket({ config }: { config: RoomsSocketConfig }) { socket.emit("leave", room); } + async function updateModel(room: string, data: any) { + const dataStr = JSON.stringify(data); + return handlers.update_model?.({ room, data: dataStr }); + } + function getListeners(room: string) { return roomsToListeners[room]; } @@ -152,7 +164,7 @@ export function RoomsSocket({ config }: { config: RoomsSocketConfig }) { socket, subscribeToRoom, updateConfig, - handlers, + updateModel, disconnect, }; } From 538095c61fc5c13b53e44ef6a93d2203bdd4ce34 Mon Sep 17 00:00:00 2001 From: Omer Katzir Date: Wed, 10 Sep 2025 11:14:31 +0300 Subject: [PATCH 06/11] type renaming --- src/client.ts | 2 +- src/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client.ts b/src/client.ts index 45b6299..694488e 100644 --- a/src/client.ts +++ b/src/client.ts @@ -12,7 +12,7 @@ export type CreateClientOptions = { onError?: (error: Error) => void; }; -export type Base44SDK = ReturnType; +export type Base44Client = ReturnType; /** * Create a Base44 client instance diff --git a/src/index.ts b/src/index.ts index 871e699..7a4b962 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import { createClient, createClientFromRequest, Base44SDK } from "./client.js"; +import { createClient, createClientFromRequest, type Base44Client } from "./client.js"; import { Base44Error } from "./utils/axios-client.js"; import { getAccessToken, @@ -17,6 +17,6 @@ export { getLoginUrl, }; -export type { Base44SDK }; +export type { Base44Client }; export * from "./types.js"; From 8685041981edea9ffe447771cfe79c3b9d212787 Mon Sep 17 00:00:00 2001 From: Omer Katzir Date: Wed, 10 Sep 2025 12:21:02 +0300 Subject: [PATCH 07/11] fix error handling --- src/utils/socket-utils.ts | 40 ++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/utils/socket-utils.ts b/src/utils/socket-utils.ts index ea9425c..0fc2f97 100644 --- a/src/utils/socket-utils.ts +++ b/src/utils/socket-utils.ts @@ -33,11 +33,7 @@ type THandler = RoomsSocketEventsMap["listen"][E]; function initializeSocket( config: RoomsSocketConfig, - handlers: Partial<{ - [k in TEvent]: ( - ...args: Parameters - ) => void; - }> + handlers: Partial ) { const socket = io(config.serverUrl, { path: config.mountPath, @@ -48,22 +44,22 @@ function initializeSocket( }, }) as Socket; - socket.on("connect", () => { + socket.on("connect", async () => { console.log("connect", socket.id); - handlers.connect?.(); + return handlers.connect?.(); }); - socket.on("update_model", (msg) => { - handlers.update_model?.(msg); + socket.on("update_model", async (msg) => { + return handlers.update_model?.(msg); }); - socket.on("error", (error) => { - handlers.error?.(error); + socket.on("error", async (error) => { + return handlers.error?.(error); }); - socket.on("connect_error", (error) => { + socket.on("connect_error", async (error) => { console.error("connect_error", error); - handlers.error?.(error); + return handlers.error?.(error); }); return socket; @@ -75,10 +71,10 @@ export function RoomsSocket({ config }: { config: RoomsSocketConfig }) { let currentConfig = { ...config }; const roomsToListeners: Record< TSocketRoom, - Partial<{ [k in TEvent]: THandler }>[] + Partial[] > = {}; - const handlers: { [k in TEvent]: THandler } = { + const handlers: RoomsSocketEventsMap["listen"] = { connect: async () => { const promises: Promise[] = []; Object.keys(roomsToListeners).forEach((room) => { @@ -100,7 +96,10 @@ export function RoomsSocket({ config }: { config: RoomsSocketConfig }) { }, error: async (error) => { console.error("error", error); - await handlers.error?.(error); + const promises = Object.values(roomsToListeners) + .flat() + .map((listener) => listener.error?.(error)); + await Promise.all(promises); }, }; @@ -154,9 +153,12 @@ export function RoomsSocket({ config }: { config: RoomsSocketConfig }) { roomsToListeners[room].push(handlers); return () => { - roomsToListeners[room] = roomsToListeners[room].filter( - (listener) => listener !== handlers - ); + roomsToListeners[room] = + roomsToListeners[room]?.filter((listener) => listener !== handlers) ?? + []; + if (roomsToListeners[room].length === 0) { + leaveRoom(room); + } }; }; From 593c0636935a79473fc71f72f275b2528829d072 Mon Sep 17 00:00:00 2001 From: Omer Katzir Date: Fri, 12 Sep 2025 11:29:39 +0300 Subject: [PATCH 08/11] added types and login redirect option --- src/modules/agents.ts | 2 +- src/modules/agents.types.ts | 43 ++++++++++++ src/modules/app.types.ts | 135 ++++++++++++++++++++++++++++++++++++ src/modules/auth.ts | 10 ++- src/modules/types.ts | 45 +----------- src/utils/axios-client.ts | 6 +- 6 files changed, 195 insertions(+), 46 deletions(-) create mode 100644 src/modules/agents.types.ts create mode 100644 src/modules/app.types.ts diff --git a/src/modules/agents.ts b/src/modules/agents.ts index 4440486..dedcc6c 100644 --- a/src/modules/agents.ts +++ b/src/modules/agents.ts @@ -1,5 +1,5 @@ import { RoomsSocket } from "../utils/socket-utils.js"; -import { AgentConversation, AgentMessage } from "./types.js"; +import { AgentConversation, AgentMessage } from "./agents.types.js"; import { AxiosInstance } from "axios"; import { ModelFilterParams } from "../types.js"; diff --git a/src/modules/agents.types.ts b/src/modules/agents.types.ts new file mode 100644 index 0000000..a6c1337 --- /dev/null +++ b/src/modules/agents.types.ts @@ -0,0 +1,43 @@ +export type AgentConversation = { + id: string; + app_id: string; + agent_name: string; + created_by_id: string; + messages: AgentMessage[]; + metadata?: Record; +}; + +export type AgentMessage = { + id: string; + role: "user" | "assistant" | "system"; + reasoning?: { + start_date: string; + end_date?: string; + content: string; + }; + content?: string | Record | null; + file_urls?: string[] | null; + tool_calls?: + | { + id: string; + name: string; + arguments_string: string; + status: "running" | "success" | "error" | "stopped"; + results?: string | null; + }[] + | null; + + usage?: { prompt_tokens?: number; completion_tokens?: number } | null; + hidden?: boolean; + custom_context?: + | { message: string; data: Record; type: string }[] + | null; + model?: string | null; + checkpoint_id?: string | null; + metadata?: { + created_date: string; + created_by_email: string; + created_by_full_name: string | null; + }; + additional_message_params?: Record; +}; diff --git a/src/modules/app.types.ts b/src/modules/app.types.ts new file mode 100644 index 0000000..fc2f360 --- /dev/null +++ b/src/modules/app.types.ts @@ -0,0 +1,135 @@ + + +export interface AppMessageContent { + content?: string; + file_urls?: string[]; + custom_context?: unknown; + additional_message_params?: Record; + [key: string]: unknown; +} + +export interface AppConversationMessage extends AppMessageContent { + id?: string | null; + role?: "user" | "assistant" | string; +} + +export interface AppConversationLike { + id?: string | null; + messages?: AppMessageContent[] | null; + model?: string; + functions_fail_silently?: boolean; +} + + +export interface DenoProjectLike { + project_id: string + project_name: string + app_id: string + deployment_name_to_info: Record + +} + +export interface AppLike { + id?: string; + conversation?: AppConversationLike | null; + app_stage?: "pending" | "product_flows" | "ready" | string; + created_date?: string; + updated_date?: string; + created_by?: string; + organization_id?: string; + name?: string; + user_description?: string; + entities?: Record; + additional_user_data_schema?: any; + pages?: { [key: string]: string }; + components: { [key: string]: any }; + layout?: string; + globals_css?: string; + agents?: Record; + logo_url?: string; + slug?: string; + public_settings?: "private_with_login" | "public_with_login" | "public_without_login" | "workspace_with_login" | string; + is_blocked?: boolean; + github_repo_url?: string; + main_page?: string; + installable_integrations?: any; + backend_project?: DenoProjectLike; + last_deployed_at?: string; + is_remixable?: boolean; + remixed_from_app_id?: string; + hide_entity_created_by?: boolean; + platform_version?: number; + enable_username_password?: boolean; + auth_config?: AuthConfigLike; + status?: { + state?: string; + details?: any; + last_updated_date?: string; + }; + custom_instructions?: any; + frozen_files?: string[]; + deep_coding_mode?: boolean; + needs_to_add_diff?: boolean; + installed_integration_context_items?: any[]; + model?: string; + is_starred?: boolean; + agents_enabled?: boolean; + categories?: string[]; + functions?: any; + function_names?: string[]; + user_entity?: UserEntityLike; + app_code_hash?: string; + has_backend_functions_enabled?: boolean; +} + +export interface UserLike { + id?: string | null; +} + +export interface UserEntityLike { + type: string; + name: string; + title?: string; + properties?: { + role?: { + type?: string; + description?: string; + enum?: ("admin" | "user" | string)[]; + }; + email?: { + type?: string; + description?: string; + }; + full_name?: { + type?: string; + description?: string; + }; + }; + required: string[]; +} + + +export interface AuthConfigLike { + enable_username_password?: boolean; + enable_google_login?: boolean; + enable_microsoft_login?: boolean; + enable_facebook_login?: boolean; + sso_provider_name?: string; + enable_sso_login?: boolean; +} + + + + +export type LoginInfoResponse = Pick< + AppLike, + | "id" + | "name" + | "slug" + | "logo_url" + | "user_description" + | "updated_date" + | "created_date" + | "auth_config" + | "platform_version" +>; \ No newline at end of file diff --git a/src/modules/auth.ts b/src/modules/auth.ts index 1927a20..db10ed0 100644 --- a/src/modules/auth.ts +++ b/src/modules/auth.ts @@ -10,7 +10,11 @@ import { AxiosInstance } from "axios"; export function createAuthModule( axios: AxiosInstance, functionsAxiosClient: AxiosInstance, - appId: string + appId: string, + options: { + serverUrl: string; + onRedirectToLogin?: () => void; + } ) { return { /** @@ -42,6 +46,10 @@ export function createAuthModule( ); } + if (options.onRedirectToLogin) { + options.onRedirectToLogin(); + return; + } // If nextUrl is not provided, use the current URL const redirectUrl = nextUrl || window.location.href; diff --git a/src/modules/types.ts b/src/modules/types.ts index a6c1337..8f24e94 100644 --- a/src/modules/types.ts +++ b/src/modules/types.ts @@ -1,43 +1,2 @@ -export type AgentConversation = { - id: string; - app_id: string; - agent_name: string; - created_by_id: string; - messages: AgentMessage[]; - metadata?: Record; -}; - -export type AgentMessage = { - id: string; - role: "user" | "assistant" | "system"; - reasoning?: { - start_date: string; - end_date?: string; - content: string; - }; - content?: string | Record | null; - file_urls?: string[] | null; - tool_calls?: - | { - id: string; - name: string; - arguments_string: string; - status: "running" | "success" | "error" | "stopped"; - results?: string | null; - }[] - | null; - - usage?: { prompt_tokens?: number; completion_tokens?: number } | null; - hidden?: boolean; - custom_context?: - | { message: string; data: Record; type: string }[] - | null; - model?: string | null; - checkpoint_id?: string | null; - metadata?: { - created_date: string; - created_by_email: string; - created_by_full_name: string | null; - }; - additional_message_params?: Record; -}; +export * from "./app.types.js"; +export * from "./agents.types.js"; \ No newline at end of file diff --git a/src/utils/axios-client.ts b/src/utils/axios-client.ts index f984901..f5711d1 100644 --- a/src/utils/axios-client.ts +++ b/src/utils/axios-client.ts @@ -90,6 +90,7 @@ export function createAxiosClient({ serverUrl, interceptResponses = true, onError, + onRedirectToLogin, }: { baseURL: string; headers?: Record; @@ -99,6 +100,7 @@ export function createAxiosClient({ serverUrl: string; interceptResponses?: boolean; onError?: (error: Error) => void; + onRedirectToLogin?: () => void; }) { const client = axios.create({ baseURL, @@ -159,7 +161,9 @@ export function createAxiosClient({ console.log("Authentication required. Redirecting to login..."); // Use a slight delay to allow the error to propagate first setTimeout(() => { - redirectToLogin(serverUrl, appId); + onRedirectToLogin + ? onRedirectToLogin() + : redirectToLogin(serverUrl, appId); }, 100); } From 2740bce8d12c6f0c59d0991dce13fa954608db03 Mon Sep 17 00:00:00 2001 From: Omer Katzir Date: Fri, 12 Sep 2025 11:31:21 +0300 Subject: [PATCH 09/11] login redirect option --- src/client.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index 9977903..fc6d1a5 100644 --- a/src/client.ts +++ b/src/client.ts @@ -32,6 +32,7 @@ export function createClient(config: { requiresAuth?: boolean; functionsVersion?: string; options?: CreateClientOptions; + onRedirectToLogin?: () => void; }) { const { serverUrl = "https://base44.app", @@ -41,6 +42,7 @@ export function createClient(config: { requiresAuth = false, options, functionsVersion, + onRedirectToLogin, } = config; const socketConfig: RoomsSocketConfig = { @@ -74,6 +76,7 @@ export function createClient(config: { appId, serverUrl, onError: options?.onError, + onRedirectToLogin, }); const functionsAxiosClient = createAxiosClient({ @@ -85,6 +88,7 @@ export function createClient(config: { serverUrl, interceptResponses: false, onError: options?.onError, + onRedirectToLogin, }); const serviceRoleAxiosClient = createAxiosClient({ @@ -94,6 +98,7 @@ export function createClient(config: { serverUrl, appId, onError: options?.onError, + onRedirectToLogin, }); const serviceRoleFunctionsAxiosClient = createAxiosClient({ @@ -103,12 +108,17 @@ export function createClient(config: { serverUrl, appId, interceptResponses: false, + onRedirectToLogin, }); const userModules = { + app: createAppModule(axiosClient, appId), entities: createEntitiesModule(axiosClient, appId), integrations: createIntegrationsModule(axiosClient, appId), - auth: createAuthModule(axiosClient, functionsAxiosClient, appId), + auth: createAuthModule(axiosClient, functionsAxiosClient, appId, { + onRedirectToLogin, + serverUrl, + }), functions: createFunctionsModule(functionsAxiosClient, appId), agents: createAgentsModule({ axios: axiosClient, From 2a332560a8a767133f779b6edeec564f99571ebd Mon Sep 17 00:00:00 2001 From: Omer Katzir Date: Fri, 12 Sep 2025 11:39:19 +0300 Subject: [PATCH 10/11] remove unused --- src/client.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index fc6d1a5..261367c 100644 --- a/src/client.ts +++ b/src/client.ts @@ -112,7 +112,6 @@ export function createClient(config: { }); const userModules = { - app: createAppModule(axiosClient, appId), entities: createEntitiesModule(axiosClient, appId), integrations: createIntegrationsModule(axiosClient, appId), auth: createAuthModule(axiosClient, functionsAxiosClient, appId, { From 337567c030105428082613faf4e013437a1fb91b Mon Sep 17 00:00:00 2001 From: Omer Katzir Date: Fri, 12 Sep 2025 11:56:06 +0300 Subject: [PATCH 11/11] versioning --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index fd67473..7c728df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@base44/sdk", - "version": "0.7.0", + "version": "0.7.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@base44/sdk", - "version": "0.7.0", + "version": "0.7.2", "license": "MIT", "dependencies": { "axios": "^1.6.2", diff --git a/package.json b/package.json index 831524a..054ddad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@base44/sdk", - "version": "0.7.0", + "version": "0.7.2", "description": "JavaScript SDK for Base44 API", "main": "dist/index.js", "types": "dist/index.d.ts",