@gravityai-dev/gravity-server
Version:
Integration SDK for the Gravity AI orchestration platform - Connect any AI platform in minutes
1,117 lines (1,099 loc) • 37.9 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
AI_RESULT_CHANNEL: () => AI_RESULT_CHANNEL,
BasePublisher: () => BasePublisher,
BatchPublisher: () => BatchPublisher,
CardPublisher: () => CardPublisher,
ChatState: () => ChatState,
EventBus: () => EventBus,
FormPublisher: () => FormPublisher,
INTERNAL_REQUEST_CHANNEL: () => INTERNAL_REQUEST_CHANNEL,
JsonDataPublisher: () => JsonDataPublisher,
MessageChunkPublisher: () => MessageChunkPublisher,
MessageType: () => MessageType,
NodeExecutionPublisher: () => NodeExecutionPublisher,
ProgressPublisher: () => ProgressPublisher,
Publisher: () => Publisher,
QUERY_MESSAGE_CHANNEL: () => QUERY_MESSAGE_CHANNEL,
QuestionsPublisher: () => QuestionsPublisher,
Redis: () => import_ioredis.default,
SYSTEM_CHANNEL: () => SYSTEM_CHANNEL,
StatePublisher: () => StatePublisher,
SystemPublisher: () => SystemPublisher,
TIMEOUTS: () => TIMEOUTS,
TYPE_TO_TYPENAME: () => TYPE_TO_TYPENAME,
TextPublisher: () => TextPublisher,
WORKFLOW_EXECUTION_CHANNEL: () => WORKFLOW_EXECUTION_CHANNEL,
WORKFLOW_STATE_CHANNEL: () => WORKFLOW_STATE_CHANNEL,
closeAllConnections: () => closeAllConnections,
createAudioChunk: () => createAudioChunk,
getBatchPublisher: () => getBatchPublisher,
getCardPublisher: () => getCardPublisher,
getFormPublisher: () => getFormPublisher,
getJsonDataPublisher: () => getJsonDataPublisher,
getMessageChunkPublisher: () => getMessageChunkPublisher,
getNodeExecutionPublisher: () => getNodeExecutionPublisher,
getOptionsFromConfig: () => getOptionsFromConfig,
getProgressPublisher: () => getProgressPublisher,
getPubSubConnection: () => getPubSubConnection,
getQuestionsPublisher: () => getQuestionsPublisher,
getRedisOptions: () => getRedisOptions,
getStandardConnection: () => getStandardConnection,
getStatePublisher: () => getStatePublisher,
getSystemPublisher: () => getSystemPublisher,
getTextPublisher: () => getTextPublisher
});
module.exports = __toCommonJS(index_exports);
// src/types.ts
var MessageType = /* @__PURE__ */ ((MessageType2) => {
MessageType2["TEXT"] = "TEXT";
MessageType2["JSON_DATA"] = "JSON_DATA";
MessageType2["IMAGE_RESPONSE"] = "IMAGE_RESPONSE";
MessageType2["TOOL_OUTPUT"] = "TOOL_OUTPUT";
MessageType2["ACTION_SUGGESTION"] = "ACTION_SUGGESTION";
MessageType2["METADATA"] = "METADATA";
MessageType2["AUDIO_CHUNK"] = "AUDIO_CHUNK";
MessageType2["SYSTEM_MESSAGE"] = "SYSTEM_MESSAGE";
MessageType2["PROGRESS_UPDATE"] = "PROGRESS_UPDATE";
MessageType2["MESSAGE_CHUNK"] = "MESSAGE_CHUNK";
MessageType2["STATE"] = "STATE";
MessageType2["CARD"] = "CARD";
MessageType2["QUESTIONS"] = "QUESTIONS";
MessageType2["FORM"] = "FORM";
MessageType2["NODE_EXECUTION_EVENT"] = "NODE_EXECUTION_EVENT";
return MessageType2;
})(MessageType || {});
var ChatState = /* @__PURE__ */ ((ChatState2) => {
ChatState2["IDLE"] = "IDLE";
ChatState2["ACTIVE"] = "ACTIVE";
ChatState2["COMPLETE"] = "COMPLETE";
ChatState2["THINKING"] = "THINKING";
ChatState2["RESPONDING"] = "RESPONDING";
ChatState2["WAITING"] = "WAITING";
ChatState2["ERROR"] = "ERROR";
ChatState2["CANCELLED"] = "CANCELLED";
return ChatState2;
})(ChatState || {});
var SYSTEM_CHANNEL = "gravity:system";
var AI_RESULT_CHANNEL = "gravity:output";
var QUERY_MESSAGE_CHANNEL = "gravity:query";
var INTERNAL_REQUEST_CHANNEL = "gravity:internal";
var WORKFLOW_EXECUTION_CHANNEL = "workflow:execution";
var WORKFLOW_STATE_CHANNEL = "gravity:workflow:state";
var TIMEOUTS = {
DEFAULT: 5e3,
REQUEST: 1e4
};
var TYPE_TO_TYPENAME = {
["TEXT" /* TEXT */]: "Text",
["MESSAGE_CHUNK" /* MESSAGE_CHUNK */]: "MessageChunk",
["JSON_DATA" /* JSON_DATA */]: "JsonData",
["ACTION_SUGGESTION" /* ACTION_SUGGESTION */]: "ActionSuggestion",
["METADATA" /* METADATA */]: "Metadata",
["IMAGE_RESPONSE" /* IMAGE_RESPONSE */]: "ImageResponse",
["TOOL_OUTPUT" /* TOOL_OUTPUT */]: "ToolOutput",
["AUDIO_CHUNK" /* AUDIO_CHUNK */]: "AudioChunk",
["STATE" /* STATE */]: "State",
["SYSTEM_MESSAGE" /* SYSTEM_MESSAGE */]: "SystemMessage",
["PROGRESS_UPDATE" /* PROGRESS_UPDATE */]: "ProgressUpdate",
["CARD" /* CARD */]: "Card",
["QUESTIONS" /* QUESTIONS */]: "Questions",
["FORM" /* FORM */]: "Form",
["NODE_EXECUTION_EVENT" /* NODE_EXECUTION_EVENT */]: "NodeExecutionEvent"
};
// src/RedisManager.ts
var import_ioredis = __toESM(require("ioredis"));
var standardConnections = /* @__PURE__ */ new Map();
var pubsubConnections = /* @__PURE__ */ new Map();
function createConfig(options) {
return {
host: options.host,
port: options.port,
password: options.password,
username: options.username,
db: options.db || 0,
retryStrategy: (times) => Math.min(times * 50, 2e3),
maxRetriesPerRequest: 3,
enableOfflineQueue: true
// Add other common options here
};
}
function getStandardConnection(options) {
const connectionKey = `${options.host}:${options.port}:${options.db || 0}:${options.username || "default"}`;
if (standardConnections.has(connectionKey)) {
return standardConnections.get(connectionKey);
}
const config = {
host: options.host,
port: options.port,
username: options.username,
password: options.password,
db: options.db || 0,
retryStrategy: (times) => Math.min(times * 50, 2e3),
maxRetriesPerRequest: 3,
enableOfflineQueue: true
};
const client = new import_ioredis.default(config);
standardConnections.set(connectionKey, client);
return client;
}
function getPubSubConnection(options) {
const connectionKey = `${options.host}:${options.port}:${options.db || 0}:${options.username || "default"}`;
if (pubsubConnections.has(connectionKey)) {
return pubsubConnections.get(connectionKey);
}
const config = createConfig(options);
const client = new import_ioredis.default(config);
pubsubConnections.set(connectionKey, client);
return client;
}
function getRedisOptions() {
return {
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT, 10),
username: process.env.REDIS_USERNAME,
password: process.env.REDIS_PASSWORD
};
}
function getOptionsFromConfig(host, port, username, password) {
return {
host,
port,
username: username || void 0,
password: password || void 0
};
}
async function closeAllConnections() {
const closePromises = [];
standardConnections.forEach((client, key) => {
closePromises.push(
client.quit().then(() => {
standardConnections.delete(key);
return `Standard connection ${key}`;
})
);
});
pubsubConnections.forEach((client, key) => {
closePromises.push(
client.quit().then(() => {
pubsubConnections.delete(key);
return `PubSub connection ${key}`;
})
);
});
await Promise.all(closePromises);
}
// src/messaging/Publisher.ts
var Publisher = class _Publisher {
constructor(options, providerId) {
this.redis = getStandardConnection(options);
this.providerId = providerId;
}
static fromRedisCredentials(redisOptions, providerId) {
return new _Publisher(redisOptions, providerId);
}
static fromConfig(host, port, password, providerId, username, db) {
const redisOptions = getOptionsFromConfig(host, port, username, password);
return new _Publisher(redisOptions, providerId);
}
getProviderId() {
return this.providerId;
}
getRedisConnection() {
return this.redis;
}
/**
* Publish system-level events
* Used by EventBus and system services
*/
async publishSystem(message) {
await this.redis.publish(SYSTEM_CHANNEL, JSON.stringify(message));
}
/**
* Publish to arbitrary event channels
* Used by EventBus, n8n resolver, and health monitor
*/
async publishEvent(eventType, payload) {
await this.redis.publish(eventType, JSON.stringify(payload));
}
async disconnect() {
this.redis = null;
}
};
// src/messaging/SimpleEventBus.ts
var EventBus = class _EventBus {
constructor(options, serviceId) {
this.options = options;
this.serviceId = serviceId;
this.handlers = /* @__PURE__ */ new Map();
this.publisher = new Publisher(options, serviceId);
this.subscriber = getPubSubConnection(options);
this.setupSubscriber();
}
static fromRedisConfig(host, port, password, serviceId, username, db) {
const options = {
host,
port,
password,
username,
db: db || 0
};
return new _EventBus(options, serviceId);
}
static fromCredentials(host, port, password, serviceId) {
return _EventBus.fromRedisConfig(host, port, password, serviceId);
}
setupSubscriber() {
this.subscriber.on("message", (channel, message) => {
const handlers = this.handlers.get(channel);
if (!handlers || handlers.size === 0) return;
try {
const event = JSON.parse(message);
handlers.forEach((handler) => {
try {
handler(event);
} catch (error) {
console.error(`[EventBus] Handler error on channel ${channel}:`, error);
}
});
} catch (error) {
console.error(`[EventBus] Failed to parse message on channel ${channel}:`, error);
}
});
}
async publish(channel, payload) {
await this.publisher.publishEvent(channel, payload);
}
async subscribe(channel, handler) {
if (!this.handlers.has(channel)) {
this.handlers.set(channel, /* @__PURE__ */ new Set());
await this.subscriber.subscribe(channel);
}
this.handlers.get(channel).add(handler);
return async () => {
const handlers = this.handlers.get(channel);
if (!handlers) return;
handlers.delete(handler);
if (handlers.size === 0) {
this.handlers.delete(channel);
await this.subscriber.unsubscribe(channel);
}
};
}
async disconnect() {
await Promise.all([
this.publisher.disconnect(),
this.subscriber.quit()
]);
}
};
// src/messaging/publishers/base.ts
var import_uuid = require("uuid");
var BasePublisher = class {
/**
* Creates a new BasePublisher instance
*
* @param {Redis} redis - Redis connection instance for publishing
* @param {string} providerId - Unique identifier for the service/provider
*
* @example
* ```typescript
* const redis = new Redis({
* host: "localhost",
* port: 6379
* });
* const publisher = new MyPublisher(redis, "my-service");
* ```
*/
constructor(redis, providerId) {
this.redis = redis;
this.providerId = providerId;
}
/**
* Gets the provider ID for the publisher
*
* This method is used by other components (like EventBus) to access
* the provider ID when creating related instances.
*
* @returns {string} The provider ID
*
* @example
* ```typescript
* const providerId = publisher.getProviderId();
* console.log(`Publisher provider: ${providerId}`);
* ```
*/
getProviderId() {
return this.providerId;
}
/**
* Gets the Redis connection for the publisher
*
* This method exposes the Redis connection for use by other components
* that need to create their own connections with the same configuration.
*
* @returns {Redis} The Redis connection instance
*
* @example
* ```typescript
* const redis = publisher.getRedisConnection();
* const options = {
* host: redis.options.host,
* port: redis.options.port
* };
* ```
*/
getRedisConnection() {
return this.redis;
}
/**
* Creates a base message with the given partial data
*
* This method constructs a complete BaseMessage by merging provided fields
* with defaults. It ensures all required fields are present and validates
* that chatId, conversationId, and userId are provided.
*
* @protected
* @param {Partial<BaseMessage>} partial - Partial message data to merge with defaults
* @returns {BaseMessage} Complete base message with all required fields
* @throws {Error} If chatId, conversationId, or userId are missing
*
* @example
* ```typescript
* const baseMessage = this.createBaseMessage({
* chatId: "chat123",
* conversationId: "conv456",
* userId: "user789",
* type: MessageType.TEXT
* });
* // Result: {
* // id: "generated-uuid",
* // chatId: "chat123",
* // conversationId: "conv456",
* // userId: "user789",
* // providerId: "my-service",
* // timestamp: "2023-12-08T10:30:00.000Z",
* // type: MessageType.TEXT
* // }
* ```
*/
createBaseMessage(partial) {
if (!partial.chatId || !partial.conversationId || !partial.userId) {
throw new Error("chatId, conversationId, and userId are required");
}
return {
id: partial.id || (0, import_uuid.v4)(),
chatId: partial.chatId,
conversationId: partial.conversationId,
userId: partial.userId,
providerId: partial.providerId || this.providerId,
timestamp: partial.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
type: partial.type || "TEXT" /* TEXT */
};
}
/**
* Publishes a message to the given channel
*
* This method serializes the message to JSON and publishes it to the
* specified Redis channel. This is the core publishing mechanism used
* by all specialized publishers.
*
* @protected
* @param {GravityMessage} message - The message object to publish
* @param {PublishOptions} [options] - Optional publishing options
* @returns {Promise<void>} Promise that resolves when message is published
*
* @example
* ```typescript
* const message = {
* __typename: "Text",
* text: "Hello, world!",
* ...baseMessage
* };
* await this.publish(message, { channel: "custom:channel" });
* ```
*/
async publish(message, options) {
const channel = options?.channel || AI_RESULT_CHANNEL;
await this.publishToStream(channel, message);
}
/**
* Publishes a message to Redis Streams for guaranteed delivery
*
* @protected
* @param {string} channel - The channel name
* @param {GravityMessage} message - The message to publish
* @returns {Promise<string>} The stream entry ID
*/
async publishToStream(channel, message) {
const streamKey = "workflow:events:stream";
try {
const conversationId = message.conversationId || "";
const entryId = await this.redis.xadd(
streamKey,
"*",
// Auto-generate ID
"channel",
channel,
"conversationId",
conversationId,
// Add for Redis Streams filtering
"message",
JSON.stringify(message),
"timestamp",
Date.now().toString(),
"providerId",
this.providerId
);
if (!entryId) {
console.error(`[BasePublisher] Failed to add entry to stream ${streamKey} - no entry ID returned`);
}
await this.redis.publish(channel, JSON.stringify(message));
return entryId || "";
} catch (error) {
console.error(`[BasePublisher] Error publishing to stream ${streamKey}:`, error);
try {
await this.redis.publish(channel, JSON.stringify(message));
console.warn(`[BasePublisher] Fell back to pub/sub only for channel ${channel}`);
} catch (pubsubError) {
console.error(`[BasePublisher] Failed to publish to pub/sub as well:`, pubsubError);
throw pubsubError;
}
return "";
}
}
};
// src/messaging/publishers/progressUpdate.ts
var ProgressPublisher = class extends BasePublisher {
/**
* Publishes a progress update
*
* @param message - The progress message text
* @param progress - Optional progress percentage (0-100)
* @param baseMessage - Base message with required fields (chatId, conversationId, userId)
* @param options - Optional publishing options (e.g., custom channel)
*/
async publishProgressUpdate(message, progress, baseMessage, options) {
const progressUpdate = {
...this.createBaseMessage(baseMessage),
__typename: "ProgressUpdate",
component: {
type: "ProgressUpdate",
props: {
message,
progress
}
}
};
await this.publish(progressUpdate, options);
}
};
var progressPublisherInstance = null;
function getProgressPublisher(host, port, password, providerId, username, db) {
if (!progressPublisherInstance) {
if (!host || !port || password === void 0 || !providerId) {
throw new Error("ProgressPublisher requires host, port, password, and providerId on first call");
}
const publisher = Publisher.fromConfig(host, port, password, providerId, username, db);
progressPublisherInstance = new ProgressPublisher(
publisher.getRedisConnection(),
publisher.getProviderId()
);
}
return progressPublisherInstance;
}
// src/messaging/publishers/messageChunk.ts
var MessageChunkPublisher = class extends BasePublisher {
/**
* Publishes a message chunk
*
* @param text - The text content of the chunk
* @param baseMessage - Base message with required fields (chatId, conversationId, userId)
* @param index - Optional sequence index for ordering
* @param options - Optional publishing options (e.g., custom channel)
*/
async publishMessageChunk(text, baseMessage, index, options) {
const messageChunk = {
...this.createBaseMessage(baseMessage),
__typename: "MessageChunk",
component: {
type: "MessageChunk",
props: {
text,
index
}
}
};
await this.publish(messageChunk, options);
}
};
var messageChunkPublisherInstance = null;
function getMessageChunkPublisher(host, port, password, providerId, username, db) {
if (!messageChunkPublisherInstance) {
if (!host || !port || password === void 0 || !providerId) {
console.error("[ERROR] MessageChunkPublisher missing required parameters:", {
hasHost: !!host,
hasPort: !!port,
hasPassword: password !== void 0,
hasProviderId: !!providerId
});
throw new Error("MessageChunkPublisher requires host, port, password, and providerId on first call");
}
console.log("[DEBUG] Creating new MessageChunkPublisher instance with Redis config:", {
host,
port,
providerId
});
const publisher = Publisher.fromConfig(host, port, password, providerId, username, db);
messageChunkPublisherInstance = new MessageChunkPublisher(
publisher.getRedisConnection(),
publisher.getProviderId()
);
} else {
console.log("[DEBUG] Returning existing MessageChunkPublisher instance");
}
return messageChunkPublisherInstance;
}
// src/messaging/publishers/text.ts
var TextPublisher = class extends BasePublisher {
/**
* Publishes a text message
*
* @param text - The text content
* @param baseMessage - Base message with required fields (chatId, conversationId, userId)
* @param options - Optional publishing options (e.g., custom channel)
*/
async publishText(text, baseMessage, options) {
const textMessage = {
...this.createBaseMessage(baseMessage),
__typename: "Text",
component: {
type: "Text",
props: {
text
}
}
};
await this.publish(textMessage, options);
}
};
var textPublisherInstance = null;
function getTextPublisher(host, port, password, providerId, username, db) {
if (!textPublisherInstance) {
if (!host || !port || password === void 0 || !providerId) {
throw new Error("TextPublisher requires host, port, password, and providerId on first call");
}
const publisher = Publisher.fromConfig(host, port, password, providerId, username, db);
textPublisherInstance = new TextPublisher(
publisher.getRedisConnection(),
publisher.getProviderId()
);
}
return textPublisherInstance;
}
// src/messaging/publishers/jsonData.ts
var JsonDataPublisher = class extends BasePublisher {
/**
* Publishes a JSON data message
*
* @param data - The JSON data to publish
* @param baseMessage - Base message with required fields (chatId, conversationId, userId)
* @param options - Optional publishing options (e.g., custom channel)
*/
async publishJsonData(data, baseMessage, options) {
const jsonMessage = {
...this.createBaseMessage(baseMessage),
__typename: "JsonData",
component: {
type: "JsonData",
props: {
data
}
}
};
await this.publish(jsonMessage, options);
}
};
var jsonDataPublisherInstance = null;
function getJsonDataPublisher(host, port, password, providerId, username, db) {
if (!jsonDataPublisherInstance) {
if (!host || !port || password === void 0 || !providerId) {
throw new Error("JsonDataPublisher requires host, port, password, and providerId on first call");
}
const publisher = Publisher.fromConfig(host, port, password, providerId, username, db);
jsonDataPublisherInstance = new JsonDataPublisher(
publisher.getRedisConnection(),
publisher.getProviderId()
);
}
return jsonDataPublisherInstance;
}
// src/messaging/publishers/audioChunk.ts
function createAudioChunk(base, audioData, format, textReference, sourceType, duration, index) {
return {
...base,
__typename: "AudioChunk",
component: {
type: "AudioChunk",
props: {
audioData,
format,
textReference,
sourceType,
duration,
index
}
}
};
}
// src/messaging/publishers/state.ts
var StatePublisher = class extends BasePublisher {
/**
* Publishes a chat state update
*
* @param state - The chat state (e.g., THINKING, RESPONDING, etc.)
* @param baseMessage - Base message with required fields (chatId, conversationId, userId)
* @param label - Optional human-readable label for the state
* @param data - Optional additional data for the state
* @param variables - Optional variables associated with the state
* @param options - Optional publishing options (e.g., custom channel)
*/
async publishState(state, baseMessage, label, data, variables, options) {
const stateMessage = {
...this.createBaseMessage(baseMessage),
__typename: "State",
component: {
type: "State",
props: {
state,
...label && { label }
}
},
...data && { data },
...label && { label },
...variables && { variables }
};
if (!stateMessage.conversationId) {
throw new Error("conversationId is required for publishing state messages");
}
await this.publish(stateMessage, options);
}
/**
* Convenience method to publish common state transitions
*/
async publishThinking(baseMessage, options) {
await this.publishState("THINKING", baseMessage, "Thinking...", void 0, void 0, options);
}
async publishResponding(baseMessage, options) {
await this.publishState("RESPONDING", baseMessage, "Responding...", void 0, void 0, options);
}
async publishWaiting(baseMessage, options) {
await this.publishState("WAITING", baseMessage, "Waiting for input...", void 0, void 0, options);
}
async publishComplete(baseMessage, options) {
await this.publishState("COMPLETE", baseMessage, "Complete", void 0, void 0, options);
}
async publishError(error, baseMessage, options) {
await this.publishState("ERROR", baseMessage, "Error occurred", { error }, void 0, options);
}
};
var statePublisherInstance = null;
function getStatePublisher(host, port, password, providerId, username, db) {
if (!statePublisherInstance) {
if (!host || !port || password === void 0 || !providerId) {
throw new Error("StatePublisher requires host, port, password, and providerId on first call");
}
const publisher = Publisher.fromConfig(host, port, password, providerId, username, db);
statePublisherInstance = new StatePublisher(
publisher.getRedisConnection(),
publisher.getProviderId()
);
}
return statePublisherInstance;
}
// src/messaging/publishers/system.ts
var SystemPublisher = class extends BasePublisher {
/**
* Publishes a system message
*
* System messages are used for service notifications, errors, and warnings.
*
* @param message - The system message text
* @param level - The message level (info, warning, or error)
* @param baseMessage - Base message with required fields (chatId, conversationId, userId)
* @param options - Optional publishing options (e.g., custom channel)
* @returns Promise that resolves when message is published
*
* @example
* ```typescript
* // Service startup notification
* await publisher.publishSystemMessage("Service started", "info", {
* chatId: "chat123",
* conversationId: "conv123",
* userId: "user456"
* });
*
* // Error notification
* await publisher.publishSystemMessage("Database connection failed", "error", {
* chatId: "chat123",
* conversationId: "conv123",
* userId: "user456"
* });
* ```
*/
async publishSystemMessage(message, level, baseMessage, options) {
const systemMessage = {
...this.createBaseMessage(baseMessage),
__typename: "SystemMessage",
message,
level
};
await this.publish(systemMessage, options);
}
};
var systemPublisherInstance = null;
function getSystemPublisher(host, port, password, providerId, username, db) {
if (!systemPublisherInstance) {
if (!host || !port || password === void 0 || !providerId) {
throw new Error("SystemPublisher requires host, port, password, and providerId on first call");
}
const publisher = Publisher.fromConfig(host, port, password, providerId, username, db);
systemPublisherInstance = new SystemPublisher(
publisher.getRedisConnection(),
publisher.getProviderId()
);
}
return systemPublisherInstance;
}
// src/messaging/publishers/cards.ts
var CardPublisher = class extends BasePublisher {
/**
* Publish card data with flexible JSON structure
* @param cardData - Any JSON structure for card data
* @param baseMessage - Base message properties
* @param options - Optional publishing options (e.g., custom channel)
*/
async publishCard(cardData, baseMessage, options) {
const message = {
...this.createBaseMessage(baseMessage),
__typename: "Cards",
component: {
type: "cards",
props: cardData
// Pass through any JSON structure
}
};
await this.publish(message, options);
}
/**
* Publish multiple cards (for workflow service compatibility)
* @param cardsData - Array of card data or single card data
* @param baseMessage - Base message properties
* @param options - Optional publishing options (e.g., custom channel)
*/
async publishCards(cardsData, baseMessage, options) {
const cardsArray = Array.isArray(cardsData) ? cardsData : [cardsData];
const message = {
...this.createBaseMessage(baseMessage),
__typename: "Cards",
component: {
type: "cards",
props: cardsArray
// Pass the entire array
}
};
await this.publish(message, options);
}
};
var cardPublisherInstance = null;
function getCardPublisher(host, port, password, providerId, username, db) {
if (!cardPublisherInstance) {
if (!host || !port || password === void 0 || !providerId) {
throw new Error("CardPublisher requires host, port, password, and providerId on first call");
}
const publisher = Publisher.fromConfig(host, port, password, providerId, username, db);
cardPublisherInstance = new CardPublisher(
publisher.getRedisConnection(),
publisher.getProviderId()
);
}
return cardPublisherInstance;
}
// src/messaging/publishers/questions.ts
var QuestionsPublisher = class extends BasePublisher {
/**
* Publish follow-up questions
* @param questions - Array of question strings
* @param baseMessage - Base message properties
* @param options - Optional publishing options (e.g., custom channel)
*/
async publishQuestions(questions, baseMessage, options) {
const message = {
...this.createBaseMessage(baseMessage),
__typename: "Questions",
component: {
type: "questions",
props: questions
// Pass the array of question strings
}
};
await this.publish(message, options);
}
};
var questionsPublisherInstance = null;
function getQuestionsPublisher(host, port, password, providerId, username, db) {
if (!questionsPublisherInstance) {
if (!host || !port || password === void 0 || !providerId) {
throw new Error("QuestionsPublisher requires host, port, password, and providerId on first call");
}
const publisher = Publisher.fromConfig(host, port, password, providerId, username, db);
questionsPublisherInstance = new QuestionsPublisher(
publisher.getRedisConnection(),
publisher.getProviderId()
);
}
return questionsPublisherInstance;
}
// src/messaging/publishers/forms.ts
var FormPublisher = class extends BasePublisher {
/**
* Publish a form structure
* @param formData - Form configuration with steps and inputs
* @param baseMessage - Base message properties
* @param options - Optional publishing options (e.g., custom channel)
*/
async publishForm(formData, baseMessage, options) {
const message = {
...this.createBaseMessage(baseMessage),
__typename: "Form",
component: {
type: "form",
props: formData
// Pass through the form structure directly
}
};
await this.publish(message, options);
}
};
var formPublisherInstance = null;
function getFormPublisher(host, port, password, providerId, username, db) {
if (!formPublisherInstance) {
if (!host || !port || password === void 0 || !providerId) {
throw new Error("FormPublisher requires host, port, password, and providerId on first call");
}
const publisher = Publisher.fromConfig(host, port, password, providerId, username, db);
formPublisherInstance = new FormPublisher(
publisher.getRedisConnection(),
publisher.getProviderId()
);
}
return formPublisherInstance;
}
// src/messaging/publishers/batch.ts
var BatchPublisher = class extends BasePublisher {
/**
* Publish multiple messages in a single batch operation
* Maximum performance - single Redis roundtrip for all messages
*
* @param messages - Array of messages to publish
* @param defaultChannel - Default channel if message doesn't specify one
*/
async publishBatch(messages, defaultChannel) {
if (messages.length === 0) return;
const pipeline = this.redis.pipeline();
for (const { message, channel } of messages) {
const targetChannel = channel || defaultChannel || message.conversationId || "gravity:output";
pipeline.publish(targetChannel, JSON.stringify(message));
}
await pipeline.exec();
}
/**
* Publish multiple messages to same channel
* Even faster when all messages go to same destination
*/
async publishBatchToChannel(messages, channel) {
if (messages.length === 0) return;
const pipeline = this.redis.pipeline();
for (const message of messages) {
pipeline.publish(channel, JSON.stringify(message));
}
await pipeline.exec();
}
/**
* Publish streaming chunks in batch
* Optimized for high-frequency message chunks
*/
async publishStreamingBatch(chunks, baseMessage, channel) {
if (chunks.length === 0) return;
const pipeline = this.redis.pipeline();
const targetChannel = channel || baseMessage.conversationId || "gravity:output";
for (const chunk of chunks) {
const message = {
...this.createBaseMessage(baseMessage),
__typename: "MessageChunk",
text: chunk
};
pipeline.publish(targetChannel, JSON.stringify(message));
}
await pipeline.exec();
}
};
var batchPublisherInstance = null;
function getBatchPublisher(host, port, password, providerId, username, db) {
if (!batchPublisherInstance) {
if (!host || !port || password === void 0 || !providerId) {
throw new Error("BatchPublisher requires host, port, password, and providerId on first call");
}
const publisher = Publisher.fromConfig(host, port, password, providerId, username, db);
batchPublisherInstance = new BatchPublisher(
publisher.getRedisConnection(),
publisher.getProviderId()
);
}
return batchPublisherInstance;
}
// src/messaging/publishers/nodeExecution.ts
var NodeExecutionPublisher = class extends BasePublisher {
/**
* Publishes a workflow node completion event
*
* @param workflowId - Workflow ID
* @param executionId - Execution ID
* @param nodeId - Node ID that completed
* @param nodeType - Type of node
* @param status - Completion status
* @param result - Node execution result
* @param error - Error message if failed
* @param duration - Execution duration in milliseconds
* @param triggeredSignals - Signals triggered by this completion
* @param baseMessage - Base message with required fields (chatId, conversationId, userId)
* @param options - Optional publishing options (e.g., custom channel)
*/
async publishNodeCompletion(workflowId, executionId, nodeId, nodeType, status, result, error, duration, triggeredSignals, baseMessage, options) {
const sanitizeOutputsForSubscription = (outputs) => {
if (!outputs || typeof outputs !== "object") return outputs;
const sanitized = { ...outputs };
if (sanitized.content && typeof sanitized.content === "string") {
const contentSize = Buffer.byteLength(sanitized.content, "utf8");
if (contentSize > 5e4) {
sanitized.content = `[CONTENT_TRUNCATED: ${contentSize} bytes - use downloadUrl]`;
}
}
return sanitized;
};
const nodeExecutionEvent = {
...baseMessage,
// Include conversationId, chatId, userId, providerId
__typename: "NodeExecutionEvent",
executionId,
workflowId,
nodeId,
nodeType,
state: status === "completed" ? "COMPLETED" : status === "running" ? "STARTED" : "ERROR",
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
duration,
outputs: status === "completed" ? sanitizeOutputsForSubscription(result) : null,
error: status === "failed" ? error : null,
triggeredSignals: triggeredSignals.map((signal) => ({
__typename: "TriggeredSignal",
targetNode: signal.targetNode,
signal: signal.signal,
inputs: signal.inputs
}))
};
const customOptions = {
...options,
channel: options?.channel || AI_RESULT_CHANNEL
};
await this.publish(nodeExecutionEvent, customOptions);
}
};
var nodeExecutionPublisherInstance = null;
function getNodeExecutionPublisher(host, port, password, providerId, username, db) {
if (!nodeExecutionPublisherInstance) {
if (!host || !port || password === void 0 || !providerId) {
throw new Error("NodeExecutionPublisher requires host, port, password, and providerId on first call");
}
const publisher = Publisher.fromConfig(host, port, password, providerId, username, db);
nodeExecutionPublisherInstance = new NodeExecutionPublisher(
publisher.getRedisConnection(),
publisher.getProviderId()
);
}
return nodeExecutionPublisherInstance;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
AI_RESULT_CHANNEL,
BasePublisher,
BatchPublisher,
CardPublisher,
ChatState,
EventBus,
FormPublisher,
INTERNAL_REQUEST_CHANNEL,
JsonDataPublisher,
MessageChunkPublisher,
MessageType,
NodeExecutionPublisher,
ProgressPublisher,
Publisher,
QUERY_MESSAGE_CHANNEL,
QuestionsPublisher,
Redis,
SYSTEM_CHANNEL,
StatePublisher,
SystemPublisher,
TIMEOUTS,
TYPE_TO_TYPENAME,
TextPublisher,
WORKFLOW_EXECUTION_CHANNEL,
WORKFLOW_STATE_CHANNEL,
closeAllConnections,
createAudioChunk,
getBatchPublisher,
getCardPublisher,
getFormPublisher,
getJsonDataPublisher,
getMessageChunkPublisher,
getNodeExecutionPublisher,
getOptionsFromConfig,
getProgressPublisher,
getPubSubConnection,
getQuestionsPublisher,
getRedisOptions,
getStandardConnection,
getStatePublisher,
getSystemPublisher,
getTextPublisher
});
//# sourceMappingURL=index.js.map