agentnet 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +265 -380
- package/_OLD_README.md +554 -0
- package/assets/network01.png +0 -0
- package/examples/customer-support/README.md +66 -0
- package/examples/customer-support/agents.yaml +457 -0
- package/examples/customer-support/index.js +408 -0
- package/examples/event-planner/README.md +69 -0
- package/examples/event-planner/agents.yaml +318 -0
- package/examples/event-planner/index.js +547 -0
- package/{src/examples → examples/smartness}/agents-smartness.yaml +8 -17
- package/{src/examples/def3.js → examples/smartness/index.js} +9 -9
- package/package.json +2 -2
- package/src/agent/agent.js +9 -1
- package/src/agent/runtime.js +2 -2
- package/src/agent/runtimes/nats.js +154 -27
- package/src/llm/gemini.js +7 -2
- package/src/llm/gpt.js +6 -1
- package/src/store/store.js +82 -48
- package/src/examples/agents.yaml +0 -394
- package/src/examples/def.js +0 -74
- package/src/examples/def2.js +0 -65
- /package/{src/examples → examples/simple}/simple.js +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentnet",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Agent library used by Smartness",
|
|
6
6
|
"main": "index.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"@nats-io/transport-node": "^3.0.2",
|
|
16
16
|
"colors": "^1.4.0",
|
|
17
17
|
"openai": "^4.97.0",
|
|
18
|
-
"pg": "^
|
|
18
|
+
"pg-promise": "^11.13.0",
|
|
19
19
|
"redis": "^5.0.1",
|
|
20
20
|
"uuid": "^11.1.0",
|
|
21
21
|
"yaml": "^2.7.1"
|
package/src/agent/agent.js
CHANGED
|
@@ -22,6 +22,7 @@ const DEFAULT_HOOKS = {
|
|
|
22
22
|
const DEFAULT_CONFIG = {
|
|
23
23
|
metadata: {
|
|
24
24
|
name: "default",
|
|
25
|
+
namespace: "default",
|
|
25
26
|
description: "A default agent"
|
|
26
27
|
},
|
|
27
28
|
runner: {
|
|
@@ -39,9 +40,10 @@ const AGENT_CONFIG_SCHEMA = {
|
|
|
39
40
|
type: 'object',
|
|
40
41
|
properties: {
|
|
41
42
|
name: { type: 'string' },
|
|
43
|
+
namespace: { type: 'string' },
|
|
42
44
|
description: { type: 'string' }
|
|
43
45
|
},
|
|
44
|
-
required: ['name']
|
|
46
|
+
required: ['name', 'namespace']
|
|
45
47
|
},
|
|
46
48
|
llm: {
|
|
47
49
|
type: 'object',
|
|
@@ -118,6 +120,12 @@ export function Agent() {
|
|
|
118
120
|
metadata: config.metadata
|
|
119
121
|
});
|
|
120
122
|
}
|
|
123
|
+
|
|
124
|
+
if (!config.metadata.namespace.trim()) {
|
|
125
|
+
throw new ConfigurationError("Agent namespace cannot be empty", {
|
|
126
|
+
metadata: config.metadata
|
|
127
|
+
});
|
|
128
|
+
}
|
|
121
129
|
|
|
122
130
|
// LLM API validation
|
|
123
131
|
if (typeof config.llm.api !== 'object' || config.llm.api === null) {
|
package/src/agent/runtime.js
CHANGED
|
@@ -8,7 +8,7 @@ export async function AgentRuntime(agentConfig) {
|
|
|
8
8
|
toolsAndHandoffsMap,
|
|
9
9
|
hooks,
|
|
10
10
|
store,
|
|
11
|
-
metadata: { name: agentName },
|
|
11
|
+
metadata: { name: agentName, namespace },
|
|
12
12
|
llm: { api: llmApi, config: llmConfig },
|
|
13
13
|
runner,
|
|
14
14
|
toolsSchemas: tools,
|
|
@@ -21,6 +21,7 @@ export async function AgentRuntime(agentConfig) {
|
|
|
21
21
|
// Initialize IO runtime
|
|
22
22
|
const natsInterfaces = ioInterfaces.filter(x => x.type === 'NatsIO')
|
|
23
23
|
const { handleTask, discoveredAgents } = await NatsIOAgentRuntime(
|
|
24
|
+
namespace,
|
|
24
25
|
agentName,
|
|
25
26
|
natsInterfaces,
|
|
26
27
|
discoverySchemas
|
|
@@ -94,7 +95,6 @@ export async function AgentRuntime(agentConfig) {
|
|
|
94
95
|
|
|
95
96
|
// Execute agent runtime
|
|
96
97
|
const result = await taskFunction(storeState.state, storeState.conversation, promptContent);
|
|
97
|
-
|
|
98
98
|
// Process result through response hook
|
|
99
99
|
const responseMessage = await response(storeState.state, storeState.conversation, result);
|
|
100
100
|
// Save session state and session data
|
|
@@ -16,10 +16,83 @@ const HEARTBEAT_INTERVAL = 1000;
|
|
|
16
16
|
const TIMEOUT_TASK_REQUEST = 60000;
|
|
17
17
|
const MAX_RECONNECT_ATTEMPTS = 5;
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Class representing a discovery message for agent capabilities
|
|
21
|
+
*/
|
|
22
|
+
class DiscoveryMessage {
|
|
23
|
+
/**
|
|
24
|
+
* Create a new discovery message
|
|
25
|
+
* @param {string} namespace - The agent namespace
|
|
26
|
+
* @param {string} agentName - The agent name
|
|
27
|
+
* @param {Array} schemas - The agent capability schemas
|
|
28
|
+
*/
|
|
29
|
+
constructor(namespace, agentName, schemas) {
|
|
30
|
+
if (!namespace) throw new Error('Namespace is required');
|
|
31
|
+
if (!agentName) throw new Error('Agent name is required');
|
|
32
|
+
if (!Array.isArray(schemas)) throw new Error('Schemas must be an array');
|
|
33
|
+
|
|
34
|
+
this.type = 'discovery';
|
|
35
|
+
this.network = `${namespace}.${agentName}`;
|
|
36
|
+
this.agentName = agentName;
|
|
37
|
+
this.schemas = schemas;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Serialize the message to a JSON string
|
|
42
|
+
* @returns {string} The serialized message
|
|
43
|
+
*/
|
|
44
|
+
serialize() {
|
|
45
|
+
return JSON.stringify({
|
|
46
|
+
type: this.type,
|
|
47
|
+
network: this.network,
|
|
48
|
+
agentName: this.agentName,
|
|
49
|
+
schemas: this.schemas
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Create a DiscoveryMessage from a serialized string
|
|
55
|
+
* @param {string} data - The serialized message data
|
|
56
|
+
* @returns {DiscoveryMessage} A new DiscoveryMessage instance
|
|
57
|
+
*/
|
|
58
|
+
static fromString(data) {
|
|
59
|
+
const payload = JSON.parse(data);
|
|
60
|
+
|
|
61
|
+
if (payload.type !== 'discovery') {
|
|
62
|
+
throw new Error('Not a discovery message');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Extract namespace from network (format: namespace.agentName)
|
|
66
|
+
const networkParts = payload.network.split('.');
|
|
67
|
+
if (networkParts.length !== 2) {
|
|
68
|
+
throw new Error('Invalid network format in discovery message');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const namespace = networkParts[0];
|
|
72
|
+
return new DiscoveryMessage(namespace, payload.agentName, payload.schemas);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Validate if a payload conforms to discovery message structure
|
|
77
|
+
* @param {Object} payload - The payload to validate
|
|
78
|
+
* @returns {boolean} Whether the payload is valid
|
|
79
|
+
*/
|
|
80
|
+
static isValid(payload) {
|
|
81
|
+
return (
|
|
82
|
+
payload &&
|
|
83
|
+
typeof payload === 'object' &&
|
|
84
|
+
payload.type === 'discovery' &&
|
|
85
|
+
typeof payload.network === 'string' &&
|
|
86
|
+
typeof payload.agentName === 'string' &&
|
|
87
|
+
Array.isArray(payload.schemas)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
19
92
|
/**
|
|
20
93
|
* Sets up discovery subscription to find other agents
|
|
21
94
|
*/
|
|
22
|
-
async function setupDiscoverySubscription(nc, discoveryTopic, agentName, discoveredAgents) {
|
|
95
|
+
async function setupDiscoverySubscription(nc, discoveryTopic, namespace, agentName, discoveredAgents, acceptedNetworks) {
|
|
23
96
|
let discoverySub;
|
|
24
97
|
|
|
25
98
|
try {
|
|
@@ -35,27 +108,84 @@ async function setupDiscoverySubscription(nc, discoveryTopic, agentName, discove
|
|
|
35
108
|
|
|
36
109
|
const handleDiscovery = async () => {
|
|
37
110
|
try {
|
|
111
|
+
let nonAcceptedNetworks = {}
|
|
38
112
|
for await (const m of discoverySub) {
|
|
39
113
|
try {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
114
|
+
// Attempt to parse and validate the discovery message
|
|
115
|
+
let discoveryMessage;
|
|
116
|
+
try {
|
|
117
|
+
discoveryMessage = DiscoveryMessage.fromString(m.string());
|
|
118
|
+
} catch (parseError) {
|
|
119
|
+
logger.warn('Invalid discovery message format', { error: parseError.message });
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const network = discoveryMessage.network;
|
|
124
|
+
const networkNamespace = network.split(".")[0];
|
|
125
|
+
const networkName = network.split(".")[1];
|
|
126
|
+
|
|
127
|
+
// Skip self
|
|
128
|
+
if (network === `${namespace}.${agentName}`) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Skip if already processed
|
|
133
|
+
if (nonAcceptedNetworks[network] === true) {
|
|
45
134
|
continue;
|
|
46
135
|
}
|
|
136
|
+
|
|
137
|
+
let isAccepted = false;
|
|
138
|
+
for (const acceptedNetwork of acceptedNetworks) {
|
|
139
|
+
const acceptedNetworkNamespace = acceptedNetwork.split(".")[0];
|
|
140
|
+
const acceptedNetworkName = acceptedNetwork.split(".")[1];
|
|
141
|
+
|
|
142
|
+
if (acceptedNetworkNamespace === networkNamespace && acceptedNetworkName === networkName) {
|
|
143
|
+
isAccepted = true;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
// Check for wildcard patterns in accepted networks
|
|
147
|
+
|
|
148
|
+
if (acceptedNetworkNamespace === '*' && acceptedNetworkName === '*') {
|
|
149
|
+
// Both namespace and name are wildcards, accept any network
|
|
150
|
+
logger.debug(`Agent ${agentName} accepting network ${network} due to wildcard pattern *.*`);
|
|
151
|
+
isAccepted = true;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (acceptedNetworkNamespace === '*' && acceptedNetworkName === networkName) {
|
|
156
|
+
// Namespace is wildcard, but name matches
|
|
157
|
+
logger.debug(`Agent ${agentName} accepting network ${network} due to wildcard pattern *.${networkName}`);
|
|
158
|
+
isAccepted = true;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (acceptedNetworkNamespace === networkNamespace && acceptedNetworkName === '*') {
|
|
163
|
+
// Name is wildcard, but namespace matches
|
|
164
|
+
logger.debug(`Agent ${agentName} accepting network ${network} due to wildcard pattern ${networkNamespace}.*`);
|
|
165
|
+
isAccepted = true;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Skip if not accepted
|
|
171
|
+
if (!isAccepted) {
|
|
172
|
+
logger.warn(`Agent ${agentName} does not accept network ${network}`);
|
|
173
|
+
nonAcceptedNetworks[network] = true;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
47
176
|
|
|
48
|
-
|
|
177
|
+
// Process the schemas from the discovery message
|
|
178
|
+
for (const schema of discoveryMessage.schemas) {
|
|
49
179
|
// Skip invalid schemas
|
|
50
180
|
if (!schema || !schema.name) {
|
|
51
181
|
logger.warn('Invalid schema in discovery payload', { schema });
|
|
52
182
|
continue;
|
|
53
183
|
}
|
|
54
184
|
|
|
55
|
-
const agentKey = `${
|
|
185
|
+
const agentKey = `${network}-${schema.name}`;
|
|
56
186
|
|
|
57
|
-
if (
|
|
58
|
-
logger.info(`${agentName} discovered agent capability: ${
|
|
187
|
+
if (discoveryMessage.agentName !== agentName && !discoveredAgents[agentKey]) {
|
|
188
|
+
logger.info(`${agentName} discovered agent capability: ${discoveryMessage.agentName} with capability ${schema.name}`);
|
|
59
189
|
|
|
60
190
|
const handoffFunction = async (conversation, state, input) => {
|
|
61
191
|
try {
|
|
@@ -70,7 +200,7 @@ async function setupDiscoverySubscription(nc, discoveryTopic, agentName, discove
|
|
|
70
200
|
content: input
|
|
71
201
|
})
|
|
72
202
|
const req = await nc.request(
|
|
73
|
-
|
|
203
|
+
discoveryMessage.agentName,
|
|
74
204
|
message.serialize(),
|
|
75
205
|
{ timeout: TIMEOUT_TASK_REQUEST }
|
|
76
206
|
);
|
|
@@ -79,7 +209,7 @@ async function setupDiscoverySubscription(nc, discoveryTopic, agentName, discove
|
|
|
79
209
|
{
|
|
80
210
|
maxRetries: 2,
|
|
81
211
|
onRetry: ({ attempt }) => {
|
|
82
|
-
logger.warn(`Retrying handoff attempt ${attempt} to ${
|
|
212
|
+
logger.warn(`Retrying handoff attempt ${attempt} to ${discoveryMessage.agentName}`, {
|
|
83
213
|
schema: schema.name
|
|
84
214
|
});
|
|
85
215
|
}
|
|
@@ -88,18 +218,18 @@ async function setupDiscoverySubscription(nc, discoveryTopic, agentName, discove
|
|
|
88
218
|
return response;
|
|
89
219
|
} catch (error) {
|
|
90
220
|
throw new HandoffError(
|
|
91
|
-
`Handoff to agent ${
|
|
221
|
+
`Handoff to agent ${discoveryMessage.agentName} failed: ${error.message}`,
|
|
92
222
|
agentName,
|
|
93
|
-
|
|
223
|
+
discoveryMessage.agentName,
|
|
94
224
|
{ schemaName: schema.name }
|
|
95
225
|
);
|
|
96
226
|
}
|
|
97
227
|
},
|
|
98
228
|
TIMEOUT_TASK_REQUEST,
|
|
99
|
-
`handoff to ${
|
|
229
|
+
`handoff to ${discoveryMessage.agentName}`
|
|
100
230
|
);
|
|
101
231
|
} catch (error) {
|
|
102
|
-
logger.error(`Handoff error to ${
|
|
232
|
+
logger.error(`Handoff error to ${discoveryMessage.agentName}`, {
|
|
103
233
|
error,
|
|
104
234
|
schema: schema.name
|
|
105
235
|
});
|
|
@@ -141,18 +271,13 @@ async function setupDiscoverySubscription(nc, discoveryTopic, agentName, discove
|
|
|
141
271
|
/**
|
|
142
272
|
* Sets up a heartbeat to announce this agent's capabilities
|
|
143
273
|
*/
|
|
144
|
-
function setupDiscoveryHeartbeat(nc, discoveryTopic, agentName, discoverySchemas) {
|
|
274
|
+
function setupDiscoveryHeartbeat(nc, discoveryTopic, namespace, agentName, discoverySchemas) {
|
|
145
275
|
let consecutiveErrors = 0;
|
|
146
276
|
|
|
147
277
|
return setInterval(async () => {
|
|
148
278
|
try {
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
agentName: agentName,
|
|
152
|
-
schemas: discoverySchemas
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
await nc.publish(discoveryTopic, JSON.stringify(heartbeatPayload));
|
|
279
|
+
const discoveryMessage = new DiscoveryMessage(namespace, agentName, discoverySchemas);
|
|
280
|
+
await nc.publish(discoveryTopic, discoveryMessage.serialize());
|
|
156
281
|
|
|
157
282
|
// Reset error counter on success
|
|
158
283
|
if (consecutiveErrors > 0) {
|
|
@@ -206,6 +331,7 @@ async function createTaskHandler(nc, agentName, processingFunction) {
|
|
|
206
331
|
if (!payload || typeof payload !== 'object') {
|
|
207
332
|
throw new Error('Invalid payload: not a JSON object');
|
|
208
333
|
}
|
|
334
|
+
|
|
209
335
|
const message = new Message(payload)
|
|
210
336
|
const input = message.getContent()
|
|
211
337
|
const session = message.getSession()
|
|
@@ -309,7 +435,7 @@ async function safeConnect(instance, options = {}) {
|
|
|
309
435
|
/**
|
|
310
436
|
* Creates a NATS-based runtime for agent communication
|
|
311
437
|
*/
|
|
312
|
-
export async function NatsIOAgentRuntime(agentName, ioInterfaces, discoverySchemas) {
|
|
438
|
+
export async function NatsIOAgentRuntime(namespace, agentName, ioInterfaces, discoverySchemas) {
|
|
313
439
|
if (ioInterfaces.length > 1) {
|
|
314
440
|
throw new TransportError(
|
|
315
441
|
'Only one IO Nats interface is supported',
|
|
@@ -342,13 +468,14 @@ export async function NatsIOAgentRuntime(agentName, ioInterfaces, discoverySchem
|
|
|
342
468
|
}
|
|
343
469
|
|
|
344
470
|
const discoveryTopic = io.config.bindings.discoveryTopic;
|
|
471
|
+
const acceptedNetworks = io.config.bindings.acceptedNetworks || [];
|
|
345
472
|
logger.info(`Agent ${agentName} initialized with discovery topic ${discoveryTopic}`);
|
|
346
473
|
|
|
347
474
|
// Step 1. Subscribe to discovery topic
|
|
348
|
-
await setupDiscoverySubscription(nc, discoveryTopic, agentName, discoveredAgents);
|
|
475
|
+
await setupDiscoverySubscription(nc, discoveryTopic, namespace, agentName, discoveredAgents, acceptedNetworks);
|
|
349
476
|
|
|
350
477
|
// Step 2. Publish discovery heartbeat
|
|
351
|
-
const interval = setupDiscoveryHeartbeat(nc, discoveryTopic, agentName, discoverySchemas);
|
|
478
|
+
const interval = setupDiscoveryHeartbeat(nc, discoveryTopic, namespace, agentName, discoverySchemas);
|
|
352
479
|
intervals.push(interval);
|
|
353
480
|
|
|
354
481
|
// Step 3. Create task handler
|
package/src/llm/gemini.js
CHANGED
|
@@ -97,8 +97,13 @@ const onResponse = async function (state, conversation, toolsAndHandoffsMap, res
|
|
|
97
97
|
if (!toolsAndHandoffsMap[name] || !toolsAndHandoffsMap[name].function) {
|
|
98
98
|
throw new Error(`Tool "${name}" not found or has no function implementation`);
|
|
99
99
|
}
|
|
100
|
-
|
|
101
|
-
let result =
|
|
100
|
+
|
|
101
|
+
let result = null
|
|
102
|
+
if (toolsAndHandoffsMap[name].type === 'handoff') {
|
|
103
|
+
result = await toolsAndHandoffsMap[name].function(conversation, state, args);
|
|
104
|
+
} else {
|
|
105
|
+
result = await toolsAndHandoffsMap[name].function(state, args);
|
|
106
|
+
}
|
|
102
107
|
if (toolsAndHandoffsMap[name].type === 'handoff') {
|
|
103
108
|
const resultParsed = JSON.parse(result)
|
|
104
109
|
// Update state with the result
|
package/src/llm/gpt.js
CHANGED
|
@@ -95,7 +95,12 @@ const onResponse = async function (state, conversation, toolsAndHandoffsMap, res
|
|
|
95
95
|
throw new Error(`Tool "${name}" not found or has no function implementation`);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
let result =
|
|
98
|
+
let result = null
|
|
99
|
+
if (toolsAndHandoffsMap[name].type === 'handoff') {
|
|
100
|
+
result = await toolsAndHandoffsMap[name].function(conversation, state, args);
|
|
101
|
+
} else {
|
|
102
|
+
result = await toolsAndHandoffsMap[name].function(state, args);
|
|
103
|
+
}
|
|
99
104
|
conversation.push(toolCall)
|
|
100
105
|
if (toolsAndHandoffsMap[name].type === 'handoff') {
|
|
101
106
|
console.log("GPT HANDOFF onResponse", name, result)
|
package/src/store/store.js
CHANGED
|
@@ -1,31 +1,6 @@
|
|
|
1
1
|
import { v4 as uuid } from 'uuid'
|
|
2
2
|
import { createClient } from 'redis'
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
let config = {
|
|
6
|
-
user: process.env.PG_USER || 'postgres',
|
|
7
|
-
host: process.env.PG_HOST || 'localhost',
|
|
8
|
-
database: process.env.PG_DATABASE || 'postgres',
|
|
9
|
-
password: process.env.PG_PASSWORD || 'password',
|
|
10
|
-
port: process.env.PG_PORT || 5433
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
if (process.env.PG_USE_SSL == 'true' || process.env.PG_USE_SSL == true) {
|
|
14
|
-
config.ssl = {
|
|
15
|
-
rejectUnauthorized: false
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const pool = new pg.Pool(config)
|
|
20
|
-
|
|
21
|
-
pool.on('error', err => {
|
|
22
|
-
console.log(new Date(), 'Lost Postgres connection', err)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
export async function getClient() {
|
|
26
|
-
const client = await pool.connect()
|
|
27
|
-
return client
|
|
28
|
-
}
|
|
3
|
+
import pgp from 'pg-promise'
|
|
29
4
|
|
|
30
5
|
export function session (id) {
|
|
31
6
|
let state = {}
|
|
@@ -117,37 +92,96 @@ export function redisStore (_config = null) {
|
|
|
117
92
|
}
|
|
118
93
|
}
|
|
119
94
|
|
|
120
|
-
export function postgresStore
|
|
121
|
-
let
|
|
122
|
-
|
|
95
|
+
export function postgresStore(config = null) {
|
|
96
|
+
let db = null;
|
|
97
|
+
const pgPromise = pgp(); // Initialize pg-promise
|
|
98
|
+
// Default connection config
|
|
99
|
+
const defaultConfig = {
|
|
100
|
+
host: process.env.PG_HOST || 'localhost',
|
|
101
|
+
port: process.env.PG_PORT || 5433,
|
|
102
|
+
database: process.env.PG_DATABASE || 'postgres',
|
|
103
|
+
user: process.env.PG_USER || 'postgres',
|
|
104
|
+
password: process.env.PG_PASSWORD || 'password',
|
|
105
|
+
max: 30, // max number of clients in the pool
|
|
106
|
+
ssl: process.env.PG_USE_SSL === 'true' ? { rejectUnauthorized: false } : null,
|
|
107
|
+
table: 'conversation_state',
|
|
108
|
+
schema: 'smartchat_agent'
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const connectionConfig = config || defaultConfig;
|
|
123
112
|
|
|
124
113
|
return {
|
|
125
|
-
connect: async function
|
|
126
|
-
if (!
|
|
127
|
-
|
|
114
|
+
connect: async function() {
|
|
115
|
+
if (!db) {
|
|
116
|
+
// For URL-style connection string
|
|
117
|
+
if (typeof connectionConfig === 'string') {
|
|
118
|
+
db = pgPromise(connectionConfig);
|
|
119
|
+
} else {
|
|
120
|
+
db = pgPromise(connectionConfig);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Test connection
|
|
124
|
+
try {
|
|
125
|
+
await db.connect();
|
|
126
|
+
|
|
127
|
+
// Create schema if not exists
|
|
128
|
+
await db.none('CREATE SCHEMA IF NOT EXISTS $1:name', [connectionConfig.schema]);
|
|
129
|
+
|
|
130
|
+
// Create table if not exists
|
|
131
|
+
await db.none(`
|
|
132
|
+
CREATE TABLE IF NOT EXISTS $1:name.$2:name (
|
|
133
|
+
id UUID PRIMARY KEY,
|
|
134
|
+
state_id TEXT,
|
|
135
|
+
state TEXT,
|
|
136
|
+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
137
|
+
)
|
|
138
|
+
`, [connectionConfig.schema, connectionConfig.table]);
|
|
139
|
+
|
|
140
|
+
// Create unique index if not exists
|
|
141
|
+
await db.none(`
|
|
142
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_acc_agent_chat_id
|
|
143
|
+
ON $1:name.$2:name(state_id)
|
|
144
|
+
`, [connectionConfig.schema, connectionConfig.table]);
|
|
145
|
+
|
|
146
|
+
} catch (error) {
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
128
149
|
}
|
|
129
150
|
},
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
151
|
+
|
|
152
|
+
disconnect: async function() {
|
|
153
|
+
if (db) {
|
|
154
|
+
await pgPromise.end();
|
|
155
|
+
db = null;
|
|
134
156
|
}
|
|
135
157
|
},
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
158
|
+
|
|
159
|
+
set: async function(key, value) {
|
|
160
|
+
const id = uuid();
|
|
161
|
+
try {
|
|
162
|
+
return await db.one(
|
|
163
|
+
'INSERT INTO $1:name.$2:name (state_id, state, id) VALUES ($3, $4, $5) ON CONFLICT (state_id) DO UPDATE SET state=$4, updated_at=CURRENT_TIMESTAMP RETURNING id',
|
|
164
|
+
[connectionConfig.schema, connectionConfig.table, key, value, id]
|
|
165
|
+
);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error('Error storing state:', error);
|
|
168
|
+
throw error;
|
|
144
169
|
}
|
|
145
|
-
|
|
146
|
-
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
get: async function(key) {
|
|
173
|
+
try {
|
|
174
|
+
const result = await db.oneOrNone(
|
|
175
|
+
'SELECT state FROM $1:name.$2:name WHERE state_id = $3',
|
|
176
|
+
[connectionConfig.schema, connectionConfig.table, key]
|
|
177
|
+
);
|
|
178
|
+
return result ? result.state : null;
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error('Error retrieving state:', error);
|
|
181
|
+
throw error;
|
|
147
182
|
}
|
|
148
|
-
return null
|
|
149
183
|
}
|
|
150
|
-
}
|
|
184
|
+
};
|
|
151
185
|
}
|
|
152
186
|
|
|
153
187
|
export function memoryStore () {
|