jorel
Version:
A unified wrapper for working with LLMs from multiple providers, including streams, images, documents & automatic tool use.
282 lines (281 loc) • 9.69 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TaskExecution = exports.__mainTaskExecutionThreadId = exports.TaskExecutionError = exports.TaskCreationError = void 0;
const providers_1 = require("../providers");
const task_execution_thread_1 = require("./task-execution-thread");
const shared_1 = require("../shared");
/** Thrown when a task creation fails */
class TaskCreationError extends Error {
constructor(message) {
super(`Task creation error: ${message}`);
}
}
exports.TaskCreationError = TaskCreationError;
/** Thrown when a task execution fails */
class TaskExecutionError extends Error {
constructor(message, taskId) {
super(`Task ${taskId}}: ${message}`);
this.taskId = taskId;
}
}
exports.TaskExecutionError = TaskExecutionError;
exports.__mainTaskExecutionThreadId = "__main__";
/**
* Represents a task execution that manages multiple threads and their events.
*/
class TaskExecution {
/**
* Create a new task execution
* @param data - The task execution definition
* @param jorEl - The agent manager instance
*/
constructor(data, jorEl) {
/** @internal */
this._modified = false;
this.id = data.id;
this._status = data.status;
this.threads = {};
for (const threadId of Object.keys(data.threads)) {
this.threads[threadId] = new task_execution_thread_1.TaskExecutionThread(data.threads[threadId], jorEl);
}
this._activeThreadId = data.activeThreadId;
this.jorEl = jorEl;
for (const thread of Object.values(this.threads)) {
if (thread.parentThreadId) {
if (!this.threads[thread.parentThreadId]) {
throw new TaskExecutionError(`Parent thread ${thread.parentThreadId} not found (thread ${thread.id})`, this.id);
}
}
if (!this.jorEl.getAgent(thread.agentId)) {
throw new TaskExecutionError(`Agent ${thread.agentId} not found (thread ${thread.id})`, this.id);
}
}
if (!this.threads[this._activeThreadId]) {
throw new TaskExecutionError(`Active thread ${this._activeThreadId} not found`, this.id);
}
this.stats = data.stats;
this._modified = data.modified;
this._haltReason = data.haltReason;
}
/**
* Get the task execution definition
*/
get status() {
return this._status;
}
/**
* Set the task execution status
*/
set status(status) {
this._status = status;
this._modified = true;
if (status === "completed") {
this._haltReason = "completed";
}
else if (status === "halted") {
this._haltReason = null;
}
}
/**
* Get the active thread ID
*/
get activeThreadId() {
return this._activeThreadId;
}
/**
* Set the active thread ID
*/
set activeThreadId(threadId) {
this._activeThreadId = threadId;
this._modified = true;
}
/**
* Whether this task execution (or any thread) has been modified
*/
get modified() {
return this._modified || Object.values(this.threads).some((thread) => thread.modified);
}
/**
* Set the modified state of this task execution
*/
set modified(modified) {
this._modified = modified;
}
/**
* Get the reason for halting the task execution
*/
get haltReason() {
return this._haltReason;
}
/**
* Set the reason for halting the task execution
*/
set haltReason(reason) {
this._haltReason = reason;
this._modified = true;
}
/**
* Events for this task execution along with aggregated usage statistics
*/
get eventsWithStatistics() {
const events = this.getEventsByThread();
const stats = this.stats;
const tokens = {};
for (const event of events) {
if (event.eventType === "generation") {
if (!tokens[event.model]) {
tokens[event.model] = {
input: 0,
output: 0,
};
}
tokens[event.model].input += event.tokenUsage.input || 0;
tokens[event.model].output += event.tokenUsage.output || 0;
}
}
return { events, stats, tokens };
}
/**
* Get the result of the task execution
*/
get result() {
if (!this.activeThread.isMain)
return null;
if (this.activeThread.latestMessage.role !== "assistant")
return null;
return this.activeThread.latestMessage.content;
}
/**
* Get the active thread for this task execution
*/
get activeThread() {
const thread = this.threads[this._activeThreadId];
if (!thread) {
throw new TaskExecutionError(`Active thread ${this._activeThreadId} not found in available threads`, this.id);
}
return thread;
}
/**
* Get the task execution definition
*/
get definition() {
const { id, status, _activeThreadId, stats } = this;
const threads = {};
for (const threadId of Object.keys(this.threads)) {
threads[threadId] = this.threads[threadId].definition;
}
return {
id,
status,
threads,
activeThreadId: _activeThreadId,
stats: { ...stats },
modified: this._modified,
haltReason: this._haltReason,
};
}
/**
* Get all events for this task execution
*/
get events() {
return this.getEventsByThread();
}
/**
* Create a new instance of this execution - e.g. to avoid modifying the original
*/
get copy() {
return new TaskExecution(this.definition, this.jorEl);
}
/**
* Get the tool calls with pending approvals for this task execution
*/
get toolCallsWithPendingApprovals() {
const toolCalls = [];
for (const thread of Object.values(this.threads)) {
toolCalls.push(...thread.toolCallsWithPendingApprovals);
}
return toolCalls;
}
/**
* Generate a new task execution from a task description
* @param task
* @param agentId
* @param jorEl
*/
static async fromTask(task, agentId, jorEl) {
return new TaskExecution({
id: (0, shared_1.generateUniqueId)(),
status: "pending",
threads: {
[exports.__mainTaskExecutionThreadId]: {
id: exports.__mainTaskExecutionThreadId,
agentId,
messages: [await (0, providers_1.generateUserMessage)(task)],
parentThreadId: null,
parentToolCallId: null,
events: [],
modified: false,
},
},
activeThreadId: exports.__mainTaskExecutionThreadId,
stats: {
generations: 0,
delegations: 0,
},
modified: false,
haltReason: null,
}, jorEl);
}
/**
* Add a follow-up user message to the main thread
* @param message
*/
async addFollowUpUserMessage(message) {
if (!this.activeThread.isMain) {
throw new TaskExecutionError("Cannot add a message to a non-main thread", this.id);
}
if (this.activeThread.latestMessage.role !== "assistant") {
throw new TaskExecutionError("The last message is not an assistant response", this.id);
}
const task = this.copy;
task.activeThread.addMessage(await (0, providers_1.generateUserMessage)(message));
task.status = "running";
return task;
}
/**
* Get events by thread
* @param threadId
*/
getEventsByThread(threadId) {
const events = [];
const threadIds = threadId ? [threadId] : Object.keys(this.threads);
for (const threadId of threadIds) {
const thread = this.threads[threadId];
if (!thread)
throw new Error(`Thread ${threadId} not found`);
const parentThread = thread.parentThreadId ? this.threads[thread.parentThreadId] : null;
for (const event of thread.events) {
events.push({ ...event, threadId, parentThreadId: parentThread?.id ?? null });
}
}
events.sort((a, b) => a.timestamp - b.timestamp);
return events;
}
/**
* Halt the task execution
* @param reason The reason for halting the task execution.
* Can be one of
* - `maxIterations`: The task execution has reached the maximum number of iterations (steps). This is useful to prevent infinite loops.
* - `maxGenerations`: The task execution has reached the maximum number of generations (model calls). This can be used to control the cost of the task execution.
* - `maxDelegations`: The task execution has reached the maximum number of delegations. This can be used to prevent excessive delegation.
* - `approvalRequired`: The task execution contains tool-calls which requires approval.
* - `invalidState`: The task execution is in an invalid state.
* - `error` : The task execution has encountered an error.
*/
halt(reason) {
this.status = "halted";
this.haltReason = reason;
return this;
}
}
exports.TaskExecution = TaskExecution;