agentnet 0.0.1

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/src/llm/gpt.js ADDED
@@ -0,0 +1,155 @@
1
+ import OpenAI from 'openai'
2
+ import { logger } from '../utils/logger.js'
3
+ import { LLMError } from '../errors/index.js'
4
+
5
+ const type = 'openai'
6
+
7
+ const getClient = async function () {
8
+ try {
9
+ if (!process.env.OPENAI_API_KEY) {
10
+ throw new LLMError(
11
+ 'OPENAI_API_KEY environment variable is not set',
12
+ 'openai'
13
+ );
14
+ }
15
+
16
+ logger.debug('Initializing OpenAI client');
17
+ return new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
18
+ } catch (error) {
19
+ logger.error('Failed to initialize OpenAI client', { error });
20
+ throw new LLMError(
21
+ `Failed to initialize OpenAI client: ${error.message}`,
22
+ 'openai',
23
+ { originalError: error }
24
+ );
25
+ }
26
+ }
27
+
28
+ const callModel = async function (llmClientConfig, context) {
29
+ const client = context.client
30
+ const toolsAndHandoffsMap = context.toolsAndHandoffsMap
31
+ const conversation = context.conversation
32
+ const input = {}
33
+ Object.assign(input, llmClientConfig)
34
+ input['tools'] = toolsAndHandoffsMap.tools
35
+ input['input'] = conversation
36
+
37
+ logger.debug('Calling OpenAI model', {
38
+ model: input.model,
39
+ conversationLength: conversation.length,
40
+ toolsCount: toolsAndHandoffsMap.tools.length
41
+ });
42
+
43
+ try {
44
+ const response = await client.responses.create(input)
45
+ logger.debug('OpenAI response received');
46
+ return response
47
+ } catch (error) {
48
+ logger.error('OpenAI API error', {
49
+ error,
50
+ modelName: input.model
51
+ });
52
+
53
+ throw new LLMError(
54
+ `OpenAI API error: ${error.message}`,
55
+ 'openai',
56
+ {
57
+ statusCode: error.status || error.statusCode,
58
+ modelName: input.model
59
+ }
60
+ );
61
+ }
62
+ }
63
+
64
+ const onResponse = async function (state, conversation, toolsAndHandoffsMap, response) {
65
+ if (response.output_text !== undefined && response.output_text.length > 0) {
66
+ logger.debug('OpenAI response contains text, returning directly');
67
+ conversation.push({ role: 'model', parts: [{ text: response.output_text }] });
68
+ return response.output_text
69
+ }
70
+
71
+ const reasoning = response.output.filter(x => x.type == 'reasoning')
72
+ const functionCalls = response.output.filter(x => x.type == 'function_call')
73
+
74
+ logger.debug('OpenAI response processing', {
75
+ reasoningCount: reasoning.length,
76
+ functionCallCount: functionCalls.length
77
+ });
78
+
79
+ for (const res of reasoning) {
80
+ conversation.push(res)
81
+ }
82
+
83
+ for (const toolCall of functionCalls) {
84
+ try {
85
+ const args = JSON.parse(toolCall.arguments)
86
+ const name = toolCall.name
87
+
88
+ logger.debug('Executing tool from OpenAI', {
89
+ toolName: name,
90
+ argsPreview: JSON.stringify(args).substring(0, 100),
91
+ callId: toolCall.call_id
92
+ });
93
+
94
+ if (!toolsAndHandoffsMap[name] || !toolsAndHandoffsMap[name].function) {
95
+ throw new Error(`Tool "${name}" not found or has no function implementation`);
96
+ }
97
+
98
+ let result = await toolsAndHandoffsMap[name].function(conversation, state, args)
99
+ conversation.push(toolCall)
100
+ if (toolsAndHandoffsMap[name].type === 'handoff') {
101
+ console.log("GPT HANDOFF onResponse", name, result)
102
+ const resultParsed = JSON.parse(result)
103
+ // Update state with the result
104
+ if (resultParsed.session) {
105
+ for (const key of Object.keys(resultParsed.session)) {
106
+ state[key] = resultParsed.session[key]
107
+ }
108
+ }
109
+ }
110
+
111
+ const resultString = typeof result == 'string' ? result : JSON.stringify(result)
112
+
113
+ logger.debug('Tool execution successful', {
114
+ toolName: name,
115
+ resultPreview: resultString.substring(0, 100)
116
+ });
117
+
118
+ conversation.push({
119
+ type: "function_call_output",
120
+ call_id: toolCall.call_id,
121
+ output: resultString
122
+ })
123
+ } catch (error) {
124
+ logger.error(`Error executing tool "${toolCall.name}"`, { error });
125
+
126
+ // Add error as function output
127
+ conversation.push(toolCall);
128
+ conversation.push({
129
+ type: "function_call_output",
130
+ call_id: toolCall.call_id,
131
+ output: JSON.stringify({ error: error.message })
132
+ });
133
+ }
134
+ }
135
+ return null
136
+ }
137
+
138
+ const prompt = async function (conversation, formattedPrompt) {
139
+ logger.debug('Adding user prompt to conversation', {
140
+ promptPreview: formattedPrompt.substring(0, 100)
141
+ });
142
+
143
+ conversation.push({
144
+ role: 'user',
145
+ content: formattedPrompt
146
+ })
147
+ }
148
+
149
+ export default {
150
+ type,
151
+ getClient,
152
+ prompt,
153
+ callModel,
154
+ onResponse
155
+ }
@@ -0,0 +1,167 @@
1
+ import { v4 as uuid } from 'uuid'
2
+ import { createClient } from 'redis'
3
+ import pg from 'pg'
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
+ }
29
+
30
+ export function session (id) {
31
+ let state = {}
32
+ let conversation = []
33
+
34
+ return {
35
+ query: async function (agentInstance, input) {
36
+ return await agentInstance.query(state, conversation, input)
37
+ },
38
+ setState: function (_state) {
39
+ state = _state
40
+ },
41
+ mergeState: function (_state) {
42
+ Object.keys(_state).forEach((key) => {
43
+ state[key] = _state[key]
44
+ })
45
+ },
46
+ getState: function () {
47
+ return state
48
+ },
49
+ trimConversation: function (elementsToKeep) {
50
+ conversation = conversation.slice(-elementsToKeep)
51
+ let additionalElementsToRemove = 0
52
+ for (const chatIndex in conversation) {
53
+ if (conversation[chatIndex].role !== 'user' || conversation[chatIndex].role == undefined) {
54
+ additionalElementsToRemove += 1
55
+ } else {
56
+ break
57
+ }
58
+ }
59
+ if (additionalElementsToRemove > 0) {
60
+ conversation = conversation.slice(additionalElementsToRemove)
61
+ }
62
+ },
63
+ setConversation: function (_conversation) {
64
+ conversation = _conversation
65
+ },
66
+ getConversation: function () {
67
+ return conversation
68
+ },
69
+ load: async function (stateStore) {
70
+ const _state = await stateStore.get(id)
71
+ if (_state !== null) {
72
+ const parsedState = JSON.parse(_state)
73
+ conversation = parsedState.conversation || []
74
+ state = parsedState.state || {}
75
+ return {
76
+ conversation: conversation,
77
+ state: state
78
+ }
79
+ }
80
+ return {
81
+ conversation: [],
82
+ state: {}
83
+ }
84
+ },
85
+ dump: async function (stateStore) {
86
+ return await stateStore.set(id, JSON.stringify({
87
+ conversation: conversation,
88
+ state: state
89
+ }))
90
+ },
91
+ }
92
+ }
93
+
94
+ export function redisStore (_config = null) {
95
+ let client = null
96
+ let config = _config
97
+
98
+ return {
99
+ connect: async function () {
100
+ if (!client) {
101
+ client = createClient(config || { url: 'redis://localhost:6379' })
102
+ client.connect()
103
+ }
104
+ },
105
+ disconnect: async function () {
106
+ if (client) {
107
+ await client.disconnect()
108
+ client = null
109
+ }
110
+ },
111
+ set: async function (key, value) {
112
+ return await client.set(key, value)
113
+ },
114
+ get: async function (key) {
115
+ return await client.get(key)
116
+ }
117
+ }
118
+ }
119
+
120
+ export function postgresStore (_config = null) {
121
+ let client = null
122
+ let config = _config
123
+
124
+ return {
125
+ connect: async function () {
126
+ if (!client) {
127
+ client = await getClient()
128
+ }
129
+ },
130
+ disconnect: async function () {
131
+ if (client) {
132
+ await client.end()
133
+ client = null
134
+ }
135
+ },
136
+ set: async function (key, value) {
137
+ const id = uuid()
138
+ return await client.query('INSERT INTO smartchat_agent.conversation_state (state_id, state, id) VALUES ($1,$2,$3) ON CONFLICT (state_id) DO UPDATE SET state_id=$1, state=$2', [key, value, id])
139
+ },
140
+ get: async function (key) {
141
+ const res = await client.query('SELECT state FROM smartchat_agent.conversation_state WHERE state_id=$1', [key])
142
+ if (res.rows.length == 1) {
143
+ return res.rows[0].state
144
+ }
145
+ if (res.rows.length > 1) {
146
+ throw 'Something went wrong at pg store'
147
+ }
148
+ return null
149
+ }
150
+ }
151
+ }
152
+
153
+ export function memoryStore () {
154
+ let state = {}
155
+
156
+ return {
157
+ connect: async function () {},
158
+ disconnect: async function () {},
159
+ set: async function (key, value) {
160
+ state[key] = value
161
+ return state[key]
162
+ },
163
+ get: async function (key) {
164
+ return state[key] || null
165
+ }
166
+ }
167
+ }
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Logging levels
3
+ */
4
+ export const LogLevel = {
5
+ ERROR: 'error',
6
+ WARN: 'warn',
7
+ INFO: 'info',
8
+ DEBUG: 'debug',
9
+ TRACE: 'trace'
10
+ };
11
+
12
+ /**
13
+ * Default logger configuration
14
+ */
15
+ const DEFAULT_CONFIG = {
16
+ level: LogLevel.INFO,
17
+ enableTimestamps: true,
18
+ enableColors: true,
19
+ redactSensitiveData: true,
20
+ sensitiveKeys: ['api_key', 'key', 'token', 'password', 'secret', 'credential'],
21
+ maxOutputLength: 1000
22
+ };
23
+
24
+ // ANSI color codes for console output
25
+ const COLORS = {
26
+ reset: '\x1b[0m',
27
+ red: '\x1b[31m',
28
+ green: '\x1b[32m',
29
+ yellow: '\x1b[33m',
30
+ blue: '\x1b[34m',
31
+ magenta: '\x1b[35m',
32
+ cyan: '\x1b[36m',
33
+ gray: '\x1b[90m'
34
+ };
35
+
36
+ /**
37
+ * Formats log message with timestamp and level
38
+ */
39
+ function formatLogMessage(level, message, config) {
40
+ let output = '';
41
+
42
+ // Add timestamp
43
+ if (config.enableTimestamps) {
44
+ const timestamp = new Date().toISOString();
45
+ output += config.enableColors ? `${COLORS.gray}${timestamp}${COLORS.reset} ` : `${timestamp} `;
46
+ }
47
+
48
+ // Add log level
49
+ if (config.enableColors) {
50
+ const levelColor = {
51
+ [LogLevel.ERROR]: COLORS.red,
52
+ [LogLevel.WARN]: COLORS.yellow,
53
+ [LogLevel.INFO]: COLORS.green,
54
+ [LogLevel.DEBUG]: COLORS.blue,
55
+ [LogLevel.TRACE]: COLORS.gray
56
+ }[level] || COLORS.reset;
57
+
58
+ output += `${levelColor}[${level.toUpperCase()}]${COLORS.reset} `;
59
+ } else {
60
+ output += `[${level.toUpperCase()}] `;
61
+ }
62
+
63
+ // Add message
64
+ output += message;
65
+
66
+ return output;
67
+ }
68
+
69
+ /**
70
+ * Redacts sensitive data in objects
71
+ */
72
+ function redactSensitiveData(data, sensitiveKeys) {
73
+ if (!data || typeof data !== 'object') {
74
+ return data;
75
+ }
76
+
77
+ if (Array.isArray(data)) {
78
+ return data.map(item => redactSensitiveData(item, sensitiveKeys));
79
+ }
80
+
81
+ // Clone the object to avoid modifying the original
82
+ const result = { ...data };
83
+
84
+ for (const key in result) {
85
+ if (Object.prototype.hasOwnProperty.call(result, key)) {
86
+ // Check if key name contains any sensitive patterns
87
+ const isSensitive = sensitiveKeys.some(pattern =>
88
+ key.toLowerCase().includes(pattern.toLowerCase())
89
+ );
90
+
91
+ if (isSensitive) {
92
+ // Redact the value
93
+ result[key] = '[REDACTED]';
94
+ } else if (typeof result[key] === 'object' && result[key] !== null) {
95
+ // Recursively process nested objects
96
+ result[key] = redactSensitiveData(result[key], sensitiveKeys);
97
+ }
98
+ }
99
+ }
100
+
101
+ return result;
102
+ }
103
+
104
+ /**
105
+ * Safely stringifies objects for logging
106
+ */
107
+ function safeStringify(obj, maxLength) {
108
+ try {
109
+ if (obj instanceof Error) {
110
+ return obj.stack || obj.message;
111
+ }
112
+
113
+ if (typeof obj === 'object' && obj !== null) {
114
+ let str = JSON.stringify(obj, null, 2);
115
+ if (maxLength && str.length > maxLength) {
116
+ str = str.substring(0, maxLength) + '... [truncated]';
117
+ }
118
+ return str;
119
+ }
120
+
121
+ return String(obj);
122
+ } catch (err) {
123
+ return `[Unstringifiable Object: ${err.message}]`;
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Creates a logger instance
129
+ */
130
+ export function createLogger(customConfig = {}) {
131
+ const config = { ...DEFAULT_CONFIG, ...customConfig };
132
+
133
+ // Log level priority map
134
+ const levelPriority = {
135
+ [LogLevel.ERROR]: 0,
136
+ [LogLevel.WARN]: 1,
137
+ [LogLevel.INFO]: 2,
138
+ [LogLevel.DEBUG]: 3,
139
+ [LogLevel.TRACE]: 4
140
+ };
141
+
142
+ /**
143
+ * Internal logging function
144
+ */
145
+ function log(level, message, data = {}) {
146
+ // Check if this log level should be shown
147
+ if (levelPriority[level] > levelPriority[config.level]) {
148
+ return;
149
+ }
150
+
151
+ // Handle error objects
152
+ if (message instanceof Error) {
153
+ data.stack = message.stack;
154
+ message = message.message;
155
+ }
156
+
157
+ // Redact sensitive data if configured
158
+ let processedData = data;
159
+ if (config.redactSensitiveData && typeof data === 'object') {
160
+ processedData = redactSensitiveData(data, config.sensitiveKeys);
161
+ }
162
+
163
+ // Format the basic message
164
+ let logMessage = formatLogMessage(level, message, config);
165
+
166
+ // Add contextual data if available
167
+ if (processedData && Object.keys(processedData).length > 0) {
168
+ logMessage += '\n' + safeStringify(processedData, config.maxOutputLength);
169
+ }
170
+
171
+ // Output to console
172
+ switch (level) {
173
+ case LogLevel.ERROR:
174
+ console.error(logMessage);
175
+ break;
176
+ case LogLevel.WARN:
177
+ console.warn(logMessage);
178
+ break;
179
+ case LogLevel.INFO:
180
+ console.info(logMessage);
181
+ break;
182
+ default:
183
+ console.log(logMessage);
184
+ }
185
+ }
186
+
187
+ return {
188
+ error: (message, data) => log(LogLevel.ERROR, message, data),
189
+ warn: (message, data) => log(LogLevel.WARN, message, data),
190
+ info: (message, data) => log(LogLevel.INFO, message, data),
191
+ debug: (message, data) => log(LogLevel.DEBUG, message, data),
192
+ trace: (message, data) => log(LogLevel.TRACE, message, data),
193
+
194
+ // Allow changing configuration at runtime
195
+ setLevel: (level) => {
196
+ if (Object.values(LogLevel).includes(level)) {
197
+ config.level = level;
198
+ } else {
199
+ log(LogLevel.WARN, `Invalid log level: ${level}. Using ${config.level}`);
200
+ }
201
+ },
202
+
203
+ // Get the current logger configuration
204
+ getConfig: () => ({ ...config })
205
+ };
206
+ }
207
+
208
+ // Create and export default logger
209
+ export const logger = createLogger();