@openserv-labs/sdk 1.0.0
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/LICENSE +21 -0
- package/README.md +608 -0
- package/dist/agent.d.ts +369 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +643 -0
- package/dist/capability.d.ts +11 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +16 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +13 -0
- package/dist/types.d.ts +1308 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +165 -0
- package/package.json +89 -0
package/dist/agent.js
ADDED
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Agent = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const compression_1 = __importDefault(require("compression"));
|
|
9
|
+
const express_1 = __importDefault(require("express"));
|
|
10
|
+
const express_async_router_1 = require("express-async-router");
|
|
11
|
+
const helmet_1 = __importDefault(require("helmet"));
|
|
12
|
+
const hpp_1 = __importDefault(require("hpp"));
|
|
13
|
+
const logger_1 = require("./logger");
|
|
14
|
+
const types_1 = require("./types");
|
|
15
|
+
const http_errors_1 = require("http-errors");
|
|
16
|
+
const zod_to_json_schema_1 = require("zod-to-json-schema");
|
|
17
|
+
const openai_1 = __importDefault(require("openai"));
|
|
18
|
+
const capability_1 = require("./capability");
|
|
19
|
+
const PLATFORM_URL = process.env.OPENSERV_API_URL || 'https://api.openserv.ai';
|
|
20
|
+
const RUNTIME_URL = process.env.OPENSERV_RUNTIME_URL || 'https://agents.openserv.ai';
|
|
21
|
+
const DEFAULT_PORT = Number.parseInt(process.env.PORT || '') || 7378;
|
|
22
|
+
class Agent {
|
|
23
|
+
options;
|
|
24
|
+
/**
|
|
25
|
+
* The Express application instance used to handle HTTP requests.
|
|
26
|
+
* This is initialized in the constructor and used to set up middleware and routes.
|
|
27
|
+
* @private
|
|
28
|
+
*/
|
|
29
|
+
app;
|
|
30
|
+
/**
|
|
31
|
+
* The HTTP server instance created from the Express application.
|
|
32
|
+
* This is initialized when start() is called and used to listen for incoming requests.
|
|
33
|
+
* @private
|
|
34
|
+
*/
|
|
35
|
+
server = null;
|
|
36
|
+
/**
|
|
37
|
+
* The Express router instance used to define API routes.
|
|
38
|
+
* This handles routing for health checks, tool execution, and action handling.
|
|
39
|
+
* @private
|
|
40
|
+
*/
|
|
41
|
+
router;
|
|
42
|
+
/**
|
|
43
|
+
* The port number the server will listen on.
|
|
44
|
+
* Defaults to DEFAULT_PORT (7378) if not specified in options.
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
47
|
+
port;
|
|
48
|
+
/**
|
|
49
|
+
* The system prompt used for OpenAI chat completions.
|
|
50
|
+
* This defines the base behavior and context for the agent.
|
|
51
|
+
* @protected
|
|
52
|
+
*/
|
|
53
|
+
systemPrompt;
|
|
54
|
+
/**
|
|
55
|
+
* Array of capabilities (tools) available to the agent.
|
|
56
|
+
* Each capability is an instance of the Capability class with a name, description, schema, and run function.
|
|
57
|
+
* @protected
|
|
58
|
+
*/
|
|
59
|
+
tools = [];
|
|
60
|
+
/**
|
|
61
|
+
* The OpenServ API key used for authentication.
|
|
62
|
+
* Can be provided in options or via OPENSERV_API_KEY environment variable.
|
|
63
|
+
* @private
|
|
64
|
+
*/
|
|
65
|
+
apiKey;
|
|
66
|
+
/**
|
|
67
|
+
* Axios instance for making requests to the OpenServ API.
|
|
68
|
+
* Pre-configured with base URL and authentication headers.
|
|
69
|
+
* @private
|
|
70
|
+
*/
|
|
71
|
+
apiClient;
|
|
72
|
+
/**
|
|
73
|
+
* Axios instance for making requests to the OpenServ Runtime API.
|
|
74
|
+
* Pre-configured with base URL and authentication headers.
|
|
75
|
+
* @protected
|
|
76
|
+
*/
|
|
77
|
+
runtimeClient;
|
|
78
|
+
/**
|
|
79
|
+
* OpenAI client instance.
|
|
80
|
+
* Lazily initialized when needed using the provided API key.
|
|
81
|
+
* @protected
|
|
82
|
+
*/
|
|
83
|
+
_openai;
|
|
84
|
+
/**
|
|
85
|
+
* Getter that converts the agent's tools into OpenAI function calling format.
|
|
86
|
+
* Used when making chat completion requests to OpenAI.
|
|
87
|
+
* @private
|
|
88
|
+
* @returns Array of ChatCompletionTool objects
|
|
89
|
+
*/
|
|
90
|
+
get openAiTools() {
|
|
91
|
+
return this.tools.map(tool => ({
|
|
92
|
+
type: 'function',
|
|
93
|
+
function: {
|
|
94
|
+
name: tool.name,
|
|
95
|
+
description: tool.description,
|
|
96
|
+
parameters: (0, zod_to_json_schema_1.zodToJsonSchema)(tool.schema)
|
|
97
|
+
}
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Getter that provides access to the OpenAI client instance.
|
|
102
|
+
* Lazily initializes the client with the API key from options or environment.
|
|
103
|
+
* @private
|
|
104
|
+
* @throws {Error} If no OpenAI API key is available
|
|
105
|
+
* @returns {OpenAI} The OpenAI client instance
|
|
106
|
+
*/
|
|
107
|
+
get openai() {
|
|
108
|
+
if (!this._openai) {
|
|
109
|
+
const apiKey = this.options.openaiApiKey || process.env.OPENAI_API_KEY;
|
|
110
|
+
if (!apiKey) {
|
|
111
|
+
throw new Error('OpenAI API key is required for process(). Please provide it in options or set OPENAI_API_KEY environment variable.');
|
|
112
|
+
}
|
|
113
|
+
this._openai = new openai_1.default({ apiKey });
|
|
114
|
+
}
|
|
115
|
+
return this._openai;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Creates a new Agent instance.
|
|
119
|
+
* Sets up the Express application, middleware, and routes.
|
|
120
|
+
* Initializes API clients with appropriate authentication.
|
|
121
|
+
*
|
|
122
|
+
* @param {AgentOptions} options - Configuration options for the agent
|
|
123
|
+
* @throws {Error} If OpenServ API key is not provided in options or environment
|
|
124
|
+
*/
|
|
125
|
+
constructor(options) {
|
|
126
|
+
this.options = options;
|
|
127
|
+
this.app = (0, express_1.default)();
|
|
128
|
+
this.router = (0, express_async_router_1.AsyncRouter)();
|
|
129
|
+
this.port = this.options.port || DEFAULT_PORT;
|
|
130
|
+
this.systemPrompt = this.options.systemPrompt;
|
|
131
|
+
this.apiKey = this.options.apiKey || process.env.OPENSERV_API_KEY || '';
|
|
132
|
+
if (!this.apiKey) {
|
|
133
|
+
throw new Error('OpenServ API key is required. Please provide it in options or set OPENSERV_API_KEY environment variable.');
|
|
134
|
+
}
|
|
135
|
+
// Initialize API client
|
|
136
|
+
this.apiClient = axios_1.default.create({
|
|
137
|
+
baseURL: PLATFORM_URL,
|
|
138
|
+
headers: {
|
|
139
|
+
'Content-Type': 'application/json',
|
|
140
|
+
'x-openserv-key': this.apiKey
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
// Initialize runtime client
|
|
144
|
+
this.runtimeClient = axios_1.default.create({
|
|
145
|
+
baseURL: `${RUNTIME_URL}/runtime`,
|
|
146
|
+
headers: {
|
|
147
|
+
'Content-Type': 'application/json',
|
|
148
|
+
'x-openserv-key': this.apiKey
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
this.app.use(express_1.default.json());
|
|
152
|
+
this.app.use(express_1.default.urlencoded({ extended: false }));
|
|
153
|
+
this.app.use((0, hpp_1.default)());
|
|
154
|
+
this.app.use((0, helmet_1.default)());
|
|
155
|
+
this.app.use((0, compression_1.default)());
|
|
156
|
+
this.setupRoutes();
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Adds a single capability (tool) to the agent.
|
|
160
|
+
* Each capability must have a unique name and defines a function that can be called via the API.
|
|
161
|
+
*
|
|
162
|
+
* @template S - The Zod schema type for the capability's parameters
|
|
163
|
+
* @param {Object} capability - The capability configuration
|
|
164
|
+
* @param {string} capability.name - Unique name for the capability
|
|
165
|
+
* @param {string} capability.description - Description of what the capability does
|
|
166
|
+
* @param {S} capability.schema - Zod schema defining the capability's parameters
|
|
167
|
+
* @param {Function} capability.run - Function that implements the capability's behavior
|
|
168
|
+
* @param {Object} capability.run.params - Parameters for the run function
|
|
169
|
+
* @param {z.infer<S>} capability.run.params.args - Validated arguments matching the schema
|
|
170
|
+
* @param {z.infer<typeof actionSchema>} [capability.run.params.action] - Optional action context
|
|
171
|
+
* @param {ChatCompletionMessageParam[]} capability.run.messages - Chat message history
|
|
172
|
+
* @returns {this} The agent instance for method chaining
|
|
173
|
+
* @throws {Error} If a capability with the same name already exists
|
|
174
|
+
*/
|
|
175
|
+
addCapability({ name, description, schema, run }) {
|
|
176
|
+
// Validate tool name uniqueness
|
|
177
|
+
if (this.tools.some(tool => tool.name === name)) {
|
|
178
|
+
throw new Error(`Tool with name "${name}" already exists`);
|
|
179
|
+
}
|
|
180
|
+
this.tools.push(new capability_1.Capability(name, description, schema, run));
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Adds multiple capabilities (tools) to the agent at once.
|
|
185
|
+
* Each capability must have a unique name and not conflict with existing capabilities.
|
|
186
|
+
*
|
|
187
|
+
* @template T - Tuple of Zod schema types for the capabilities' parameters
|
|
188
|
+
* @param {Object} capabilities - Array of capability configurations
|
|
189
|
+
* @param {string} capabilities[].name - Unique name for each capability
|
|
190
|
+
* @param {string} capabilities[].description - Description of what each capability does
|
|
191
|
+
* @param {T[number]} capabilities[].schema - Zod schema defining each capability's parameters
|
|
192
|
+
* @param {Function} capabilities[].run - Function that implements each capability's behavior
|
|
193
|
+
* @returns {this} The agent instance for method chaining
|
|
194
|
+
* @throws {Error} If any capability has a name that already exists
|
|
195
|
+
*/
|
|
196
|
+
addCapabilities(capabilities) {
|
|
197
|
+
capabilities.forEach(capability => {
|
|
198
|
+
this.addCapability(capability);
|
|
199
|
+
});
|
|
200
|
+
return this;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Gets files in a workspace.
|
|
204
|
+
*
|
|
205
|
+
* @param {GetFilesParams} params - Parameters for the file retrieval
|
|
206
|
+
* @param {number} params.workspaceId - ID of the workspace to get files from
|
|
207
|
+
* @returns {Promise<any>} The files in the workspace
|
|
208
|
+
*/
|
|
209
|
+
async getFiles(params) {
|
|
210
|
+
const response = await this.apiClient.get(`/workspaces/${params.workspaceId}/files`);
|
|
211
|
+
return response.data;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Uploads a file to a workspace.
|
|
215
|
+
*
|
|
216
|
+
* @param {UploadFileParams} params - Parameters for the file upload
|
|
217
|
+
* @param {number} params.workspaceId - ID of the workspace to upload to
|
|
218
|
+
* @param {string} params.path - Path where the file should be stored
|
|
219
|
+
* @param {number[]|number|null} [params.taskIds] - Optional task IDs to associate with the file
|
|
220
|
+
* @param {boolean} [params.skipSummarizer] - Whether to skip file summarization
|
|
221
|
+
* @param {Buffer|string} params.file - The file content to upload
|
|
222
|
+
* @returns {Promise<any>} The uploaded file details
|
|
223
|
+
*/
|
|
224
|
+
async uploadFile(params) {
|
|
225
|
+
const formData = new FormData();
|
|
226
|
+
formData.append('path', params.path);
|
|
227
|
+
if (params.taskIds) {
|
|
228
|
+
formData.append('taskIds', JSON.stringify(params.taskIds));
|
|
229
|
+
}
|
|
230
|
+
if (params.skipSummarizer !== undefined) {
|
|
231
|
+
formData.append('skipSummarizer', params.skipSummarizer.toString());
|
|
232
|
+
}
|
|
233
|
+
// Convert Buffer or string to Blob for FormData
|
|
234
|
+
const fileBlob = params.file instanceof Buffer
|
|
235
|
+
? new Blob([params.file])
|
|
236
|
+
: new Blob([params.file], { type: 'text/plain' });
|
|
237
|
+
formData.append('file', fileBlob);
|
|
238
|
+
const response = await this.apiClient.post(`/workspaces/${params.workspaceId}/file`, formData, {
|
|
239
|
+
headers: {
|
|
240
|
+
'Content-Type': 'multipart/form-data'
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
return response.data;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Marks a task as errored.
|
|
247
|
+
*
|
|
248
|
+
* @param {MarkTaskAsErroredParams} params - Parameters for marking the task as errored
|
|
249
|
+
* @param {number} params.workspaceId - ID of the workspace containing the task
|
|
250
|
+
* @param {number} params.taskId - ID of the task to mark as errored
|
|
251
|
+
* @param {string} params.error - Error message describing what went wrong
|
|
252
|
+
* @returns {Promise<any>} The updated task details
|
|
253
|
+
*/
|
|
254
|
+
async markTaskAsErrored(params) {
|
|
255
|
+
const response = await this.apiClient.post(`/workspaces/${params.workspaceId}/tasks/${params.taskId}/error`, {
|
|
256
|
+
error: params.error
|
|
257
|
+
});
|
|
258
|
+
return response.data;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Completes a task with the specified output.
|
|
262
|
+
*
|
|
263
|
+
* @param {CompleteTaskParams} params - Parameters for completing the task
|
|
264
|
+
* @param {number} params.workspaceId - ID of the workspace containing the task
|
|
265
|
+
* @param {number} params.taskId - ID of the task to complete
|
|
266
|
+
* @param {string} params.output - Output or result of the completed task
|
|
267
|
+
* @returns {Promise<any>} The completed task details
|
|
268
|
+
*/
|
|
269
|
+
async completeTask(params) {
|
|
270
|
+
const response = await this.apiClient.put(`/workspaces/${params.workspaceId}/tasks/${params.taskId}/complete`, {
|
|
271
|
+
output: params.output
|
|
272
|
+
});
|
|
273
|
+
return response.data;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Sends a chat message from the agent.
|
|
277
|
+
*
|
|
278
|
+
* @param {SendChatMessageParams} params - Parameters for sending the chat message
|
|
279
|
+
* @param {number} params.workspaceId - ID of the workspace where the chat is happening
|
|
280
|
+
* @param {number} params.agentId - ID of the agent sending the message
|
|
281
|
+
* @param {string} params.message - Content of the message to send
|
|
282
|
+
* @returns {Promise<any>} The sent message details
|
|
283
|
+
*/
|
|
284
|
+
async sendChatMessage(params) {
|
|
285
|
+
const response = await this.apiClient.post(`/workspaces/${params.workspaceId}/agent-chat/${params.agentId}/message`, {
|
|
286
|
+
message: params.message
|
|
287
|
+
});
|
|
288
|
+
return response.data;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Gets detailed information about a specific task.
|
|
292
|
+
*
|
|
293
|
+
* @param {GetTaskDetailParams} params - Parameters for getting task details
|
|
294
|
+
* @param {number} params.workspaceId - ID of the workspace containing the task
|
|
295
|
+
* @param {number} params.taskId - ID of the task to get details for
|
|
296
|
+
* @returns {Promise<any>} The detailed task information
|
|
297
|
+
*/
|
|
298
|
+
async getTaskDetail(params) {
|
|
299
|
+
const response = await this.apiClient.get(`/workspaces/${params.workspaceId}/tasks/${params.taskId}/detail`);
|
|
300
|
+
return response.data;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Gets a list of agents in a workspace.
|
|
304
|
+
*
|
|
305
|
+
* @param {GetAgentsParams} params - Parameters for getting agents
|
|
306
|
+
* @param {number} params.workspaceId - ID of the workspace to get agents from
|
|
307
|
+
* @returns {Promise<any>} List of agents in the workspace
|
|
308
|
+
*/
|
|
309
|
+
async getAgents(params) {
|
|
310
|
+
const response = await this.apiClient.get(`/workspaces/${params.workspaceId}/agents`);
|
|
311
|
+
return response.data;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Gets a list of tasks in a workspace.
|
|
315
|
+
*
|
|
316
|
+
* @param {GetTasksParams} params - Parameters for getting tasks
|
|
317
|
+
* @param {number} params.workspaceId - ID of the workspace to get tasks from
|
|
318
|
+
* @returns {Promise<any>} List of tasks in the workspace
|
|
319
|
+
*/
|
|
320
|
+
async getTasks(params) {
|
|
321
|
+
const response = await this.apiClient.get(`/workspaces/${params.workspaceId}/tasks`);
|
|
322
|
+
return response.data;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Creates a new task in a workspace.
|
|
326
|
+
*
|
|
327
|
+
* @param {CreateTaskParams} params - Parameters for creating the task
|
|
328
|
+
* @param {number} params.workspaceId - ID of the workspace to create the task in
|
|
329
|
+
* @param {number} params.assignee - ID of the agent to assign the task to
|
|
330
|
+
* @param {string} params.description - Short description of the task
|
|
331
|
+
* @param {string} params.body - Detailed body/content of the task
|
|
332
|
+
* @param {string} params.input - Input data for the task
|
|
333
|
+
* @param {string} params.expectedOutput - Expected output format or content
|
|
334
|
+
* @param {number[]} params.dependencies - IDs of tasks that this task depends on
|
|
335
|
+
* @returns {Promise<any>} The created task details
|
|
336
|
+
*/
|
|
337
|
+
async createTask(params) {
|
|
338
|
+
const response = await this.apiClient.post(`/workspaces/${params.workspaceId}/task`, {
|
|
339
|
+
assignee: params.assignee,
|
|
340
|
+
description: params.description,
|
|
341
|
+
body: params.body,
|
|
342
|
+
input: params.input,
|
|
343
|
+
expectedOutput: params.expectedOutput,
|
|
344
|
+
dependencies: params.dependencies
|
|
345
|
+
});
|
|
346
|
+
return response.data;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Adds a log entry to a task.
|
|
350
|
+
*
|
|
351
|
+
* @param {AddLogToTaskParams} params - Parameters for adding the log
|
|
352
|
+
* @param {number} params.workspaceId - ID of the workspace containing the task
|
|
353
|
+
* @param {number} params.taskId - ID of the task to add the log to
|
|
354
|
+
* @param {'info'|'warning'|'error'} params.severity - Severity level of the log
|
|
355
|
+
* @param {'text'|'openai-message'} params.type - Type of log entry
|
|
356
|
+
* @param {string|object} params.body - Content of the log entry
|
|
357
|
+
* @returns {Promise<any>} The created log entry details
|
|
358
|
+
*/
|
|
359
|
+
async addLogToTask(params) {
|
|
360
|
+
const response = await this.apiClient.post(`/workspaces/${params.workspaceId}/tasks/${params.taskId}/log`, {
|
|
361
|
+
severity: params.severity,
|
|
362
|
+
type: params.type,
|
|
363
|
+
body: params.body
|
|
364
|
+
});
|
|
365
|
+
return response.data;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Requests human assistance for a task.
|
|
369
|
+
*
|
|
370
|
+
* @param {RequestHumanAssistanceParams} params - Parameters for requesting assistance
|
|
371
|
+
* @param {number} params.workspaceId - ID of the workspace containing the task
|
|
372
|
+
* @param {number} params.taskId - ID of the task needing assistance
|
|
373
|
+
* @param {'text'|'project-manager-plan-review'} params.type - Type of assistance needed
|
|
374
|
+
* @param {string|object} params.question - Question or request for the human
|
|
375
|
+
* @param {object} [params.agentDump] - Optional agent state/context information
|
|
376
|
+
* @returns {Promise<any>} The created assistance request details
|
|
377
|
+
*/
|
|
378
|
+
async requestHumanAssistance(params) {
|
|
379
|
+
const response = await this.apiClient.post(`/workspaces/${params.workspaceId}/tasks/${params.taskId}/human-assistance`, {
|
|
380
|
+
type: params.type,
|
|
381
|
+
question: params.question,
|
|
382
|
+
agentDump: params.agentDump
|
|
383
|
+
});
|
|
384
|
+
return response.data;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Updates the status of a task.
|
|
388
|
+
*
|
|
389
|
+
* @param {UpdateTaskStatusParams} params - Parameters for updating the status
|
|
390
|
+
* @param {number} params.workspaceId - ID of the workspace containing the task
|
|
391
|
+
* @param {number} params.taskId - ID of the task to update
|
|
392
|
+
* @param {TaskStatus} params.status - New status for the task
|
|
393
|
+
* @returns {Promise<any>} The updated task details
|
|
394
|
+
*/
|
|
395
|
+
async updateTaskStatus(params) {
|
|
396
|
+
const response = await this.apiClient.put(`/workspaces/${params.workspaceId}/tasks/${params.taskId}/status`, {
|
|
397
|
+
status: params.status
|
|
398
|
+
});
|
|
399
|
+
return response.data;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Processes a conversation with OpenAI, handling tool calls iteratively until completion.
|
|
403
|
+
*
|
|
404
|
+
* @param {ProcessParams} params - Parameters for processing the conversation
|
|
405
|
+
* @param {ChatCompletionMessageParam[]} params.messages - The conversation history
|
|
406
|
+
* @returns {Promise<ChatCompletion>} The final response from OpenAI
|
|
407
|
+
* @throws {Error} If no response is received from OpenAI or max iterations are reached
|
|
408
|
+
*/
|
|
409
|
+
async process({ messages }) {
|
|
410
|
+
const currentMessages = [...messages];
|
|
411
|
+
let completion = null;
|
|
412
|
+
let iterationCount = 0;
|
|
413
|
+
const MAX_ITERATIONS = 10;
|
|
414
|
+
while (iterationCount < MAX_ITERATIONS) {
|
|
415
|
+
completion = await this.openai.chat.completions.create({
|
|
416
|
+
model: 'gpt-4o',
|
|
417
|
+
messages: currentMessages,
|
|
418
|
+
tools: this.openAiTools
|
|
419
|
+
});
|
|
420
|
+
if (!completion.choices?.length || !completion.choices[0]?.message) {
|
|
421
|
+
throw new Error('No response from OpenAI');
|
|
422
|
+
}
|
|
423
|
+
const lastMessage = completion.choices[0].message;
|
|
424
|
+
// If there are no tool calls, we're done
|
|
425
|
+
if (!lastMessage.tool_calls?.length) {
|
|
426
|
+
return completion;
|
|
427
|
+
}
|
|
428
|
+
// Process each tool call
|
|
429
|
+
const toolResults = await Promise.all(lastMessage.tool_calls.map(async (toolCall) => {
|
|
430
|
+
// We know the function exists because we provided the tools
|
|
431
|
+
const { name, arguments: args } = toolCall.function;
|
|
432
|
+
const parsedArgs = JSON.parse(args);
|
|
433
|
+
try {
|
|
434
|
+
// Call the corresponding method on this class with named parameters
|
|
435
|
+
const result = await this[name](parsedArgs);
|
|
436
|
+
return {
|
|
437
|
+
tool_call_id: toolCall.id,
|
|
438
|
+
role: 'tool',
|
|
439
|
+
name,
|
|
440
|
+
content: JSON.stringify(result)
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
445
|
+
logger_1.logger.error({ error, toolCall }, 'Error executing tool call');
|
|
446
|
+
return {
|
|
447
|
+
tool_call_id: toolCall.id,
|
|
448
|
+
role: 'tool',
|
|
449
|
+
name,
|
|
450
|
+
content: JSON.stringify({ error: errorMessage })
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
}));
|
|
454
|
+
// Add the assistant's message and tool results to the conversation
|
|
455
|
+
currentMessages.push(lastMessage, ...toolResults);
|
|
456
|
+
iterationCount++;
|
|
457
|
+
}
|
|
458
|
+
throw new Error('Max iterations reached without completion');
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Handle a task execution request
|
|
462
|
+
* This method can be overridden by extending classes to customize task handling
|
|
463
|
+
* @protected
|
|
464
|
+
*/
|
|
465
|
+
async doTask(action) {
|
|
466
|
+
const messages = [
|
|
467
|
+
{
|
|
468
|
+
role: 'system',
|
|
469
|
+
content: this.systemPrompt
|
|
470
|
+
}
|
|
471
|
+
];
|
|
472
|
+
if (action.task?.description) {
|
|
473
|
+
messages.push({
|
|
474
|
+
role: 'user',
|
|
475
|
+
content: action.task.description
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
try {
|
|
479
|
+
await this.runtimeClient.post('/execute', {
|
|
480
|
+
tools: this.tools.map(convertToolToJsonSchema),
|
|
481
|
+
messages,
|
|
482
|
+
action
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
catch (error) {
|
|
486
|
+
if (action.task) {
|
|
487
|
+
await this.markTaskAsErrored({
|
|
488
|
+
workspaceId: action.workspace.id,
|
|
489
|
+
taskId: action.task.id,
|
|
490
|
+
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
logger_1.logger.error({ error }, 'Failed to forward action to runtime agent');
|
|
494
|
+
throw error;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Handle a chat message response request
|
|
499
|
+
* This method can be overridden by extending classes to customize chat handling
|
|
500
|
+
* @protected
|
|
501
|
+
*/
|
|
502
|
+
async respondToChat(action) {
|
|
503
|
+
const messages = [
|
|
504
|
+
{
|
|
505
|
+
role: 'system',
|
|
506
|
+
content: this.systemPrompt
|
|
507
|
+
}
|
|
508
|
+
];
|
|
509
|
+
if (action.messages) {
|
|
510
|
+
action.messages.forEach((msg) => {
|
|
511
|
+
messages.push({
|
|
512
|
+
role: msg.author === 'user' ? 'user' : 'assistant',
|
|
513
|
+
content: msg.message
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
try {
|
|
518
|
+
await this.runtimeClient.post('/chat', {
|
|
519
|
+
tools: this.tools.map(convertToolToJsonSchema),
|
|
520
|
+
messages,
|
|
521
|
+
action
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
catch (error) {
|
|
525
|
+
await this.sendChatMessage({
|
|
526
|
+
workspaceId: action.workspace.id,
|
|
527
|
+
agentId: action.me.id,
|
|
528
|
+
message: error instanceof Error ? error.message : 'Unknown error occurred'
|
|
529
|
+
});
|
|
530
|
+
logger_1.logger.error({ error }, 'Failed to forward action to runtime agent');
|
|
531
|
+
throw error;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Handles execution of a specific tool/capability.
|
|
536
|
+
*
|
|
537
|
+
* @param {Object} req - The request object
|
|
538
|
+
* @param {Object} req.params - Request parameters
|
|
539
|
+
* @param {string} req.params.toolName - Name of the tool to execute
|
|
540
|
+
* @param {Object} req.body - Request body
|
|
541
|
+
* @param {z.infer<z.ZodTypeAny>} [req.body.args] - Arguments for the tool
|
|
542
|
+
* @param {z.infer<typeof actionSchema>} [req.body.action] - Action context
|
|
543
|
+
* @returns {Promise<{result: string}>} The result of the tool execution
|
|
544
|
+
* @throws {BadRequest} If tool name is missing or tool is not found
|
|
545
|
+
* @throws {Error} If tool execution fails
|
|
546
|
+
*/
|
|
547
|
+
async handleToolRoute(req) {
|
|
548
|
+
if (!('toolName' in req.params)) {
|
|
549
|
+
throw new http_errors_1.BadRequest('Tool name is required');
|
|
550
|
+
}
|
|
551
|
+
const tool = this.tools.find(t => t.name === req.params.toolName);
|
|
552
|
+
if (!tool) {
|
|
553
|
+
throw new http_errors_1.BadRequest(`Tool "${req.params.toolName}" not found`);
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
const args = await tool.schema.parseAsync(req.body?.args);
|
|
557
|
+
const result = await tool.run.call(this, { args, action: req.body.action }, []);
|
|
558
|
+
return { result };
|
|
559
|
+
}
|
|
560
|
+
catch (error) {
|
|
561
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
562
|
+
throw new Error(errorMessage);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Handles the root route for task execution and chat message responses.
|
|
567
|
+
*
|
|
568
|
+
* @param {Object} req - The request object
|
|
569
|
+
* @param {unknown} req.body - Request body to be parsed as an action
|
|
570
|
+
* @returns {Promise<void>}
|
|
571
|
+
* @throws {Error} If action type is invalid
|
|
572
|
+
*/
|
|
573
|
+
async handleRootRoute(req) {
|
|
574
|
+
const action = await types_1.actionSchema.parseAsync(req.body);
|
|
575
|
+
if (action.type === 'do-task') {
|
|
576
|
+
this.doTask(action);
|
|
577
|
+
}
|
|
578
|
+
else if (action.type === 'respond-chat-message') {
|
|
579
|
+
this.respondToChat(action);
|
|
580
|
+
}
|
|
581
|
+
else
|
|
582
|
+
throw new Error('Invalid action type');
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Sets up the Express routes for the agent's HTTP server.
|
|
586
|
+
* Configures health check endpoint and routes for tool execution.
|
|
587
|
+
* @private
|
|
588
|
+
*/
|
|
589
|
+
setupRoutes() {
|
|
590
|
+
this.router.get('/health', async (_req, res) => {
|
|
591
|
+
res.status(200).json({ status: 'ok', uptime: process.uptime() });
|
|
592
|
+
});
|
|
593
|
+
this.router.post('/', async (req) => {
|
|
594
|
+
return this.handleRootRoute({ body: req.body });
|
|
595
|
+
});
|
|
596
|
+
this.router.post('/tools/:toolName', async (req) => {
|
|
597
|
+
const { toolName } = req.params;
|
|
598
|
+
if (!toolName) {
|
|
599
|
+
throw new http_errors_1.BadRequest('Tool name is required');
|
|
600
|
+
}
|
|
601
|
+
return this.handleToolRoute({
|
|
602
|
+
params: { toolName },
|
|
603
|
+
body: req.body
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
this.app.use('/', this.router);
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Starts the agent's HTTP server.
|
|
610
|
+
*
|
|
611
|
+
* @returns {Promise<void>} Resolves when the server has started
|
|
612
|
+
* @throws {Error} If server fails to start
|
|
613
|
+
*/
|
|
614
|
+
async start() {
|
|
615
|
+
return new Promise((resolve, reject) => {
|
|
616
|
+
this.server = this.app.listen(this.port, () => {
|
|
617
|
+
logger_1.logger.info(`Agent server started on port ${this.port}`);
|
|
618
|
+
resolve();
|
|
619
|
+
});
|
|
620
|
+
this.server.on('error', reject);
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Stops the agent's HTTP server.
|
|
625
|
+
*
|
|
626
|
+
* @returns {Promise<void>} Resolves when the server has stopped
|
|
627
|
+
*/
|
|
628
|
+
async stop() {
|
|
629
|
+
if (!this.server)
|
|
630
|
+
return;
|
|
631
|
+
return new Promise(resolve => {
|
|
632
|
+
this.server?.close(() => resolve());
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
exports.Agent = Agent;
|
|
637
|
+
function convertToolToJsonSchema(tool) {
|
|
638
|
+
return {
|
|
639
|
+
name: tool.name,
|
|
640
|
+
description: tool.description,
|
|
641
|
+
schema: (0, zod_to_json_schema_1.zodToJsonSchema)(tool.schema)
|
|
642
|
+
};
|
|
643
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ChatCompletionMessageParam } from 'openai/resources/chat/completions';
|
|
3
|
+
import type { CapabilityFuncParams } from './types';
|
|
4
|
+
export declare class Capability<Schema extends z.ZodTypeAny> {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly description: string;
|
|
7
|
+
readonly schema: Schema;
|
|
8
|
+
readonly run: (params: CapabilityFuncParams<Schema>, messages: ChatCompletionMessageParam[]) => string | Promise<string>;
|
|
9
|
+
constructor(name: string, description: string, schema: Schema, run: (params: CapabilityFuncParams<Schema>, messages: ChatCompletionMessageParam[]) => string | Promise<string>);
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=capability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,mCAAmC,CAAA;AACnF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAEnD,qBAAa,UAAU,CAAC,MAAM,SAAS,CAAC,CAAC,UAAU;aAE/B,IAAI,EAAE,MAAM;aACZ,WAAW,EAAE,MAAM;aACnB,MAAM,EAAE,MAAM;aACd,GAAG,EAAE,CACnB,MAAM,EAAE,oBAAoB,CAAC,MAAM,CAAC,EACpC,QAAQ,EAAE,0BAA0B,EAAE,KACnC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;gBANb,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,CACnB,MAAM,EAAE,oBAAoB,CAAC,MAAM,CAAC,EACpC,QAAQ,EAAE,0BAA0B,EAAE,KACnC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAEhC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Capability = void 0;
|
|
4
|
+
class Capability {
|
|
5
|
+
name;
|
|
6
|
+
description;
|
|
7
|
+
schema;
|
|
8
|
+
run;
|
|
9
|
+
constructor(name, description, schema, run) {
|
|
10
|
+
this.name = name;
|
|
11
|
+
this.description = description;
|
|
12
|
+
this.schema = schema;
|
|
13
|
+
this.run = run;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.Capability = Capability;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { Agent } from './agent';
|
|
2
|
+
export type { AgentOptions } from './agent';
|
|
3
|
+
export { Capability } from './capability';
|
|
4
|
+
export type { TaskStatus, GetFilesParams, UploadFileParams, MarkTaskAsErroredParams, CompleteTaskParams, SendChatMessageParams, GetTaskDetailParams, GetAgentsParams, GetTasksParams, CreateTaskParams, AddLogToTaskParams, RequestHumanAssistanceParams, UpdateTaskStatusParams, ProcessParams, CapabilityFuncParams } from './types';
|
|
5
|
+
export { actionSchema, doTaskActionSchema, respondChatMessageActionSchema, taskStatusSchema, agentKind } from './types';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzC,YAAY,EACV,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,uBAAuB,EACvB,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,4BAA4B,EAC5B,sBAAsB,EACtB,aAAa,EACb,oBAAoB,EACrB,MAAM,SAAS,CAAA;AAEhB,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,8BAA8B,EAC9B,gBAAgB,EAChB,SAAS,EACV,MAAM,SAAS,CAAA"}
|