Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions examples/src/anchor-browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { createAnthropic } from "@ai-sdk/anthropic";
import { createVercelAIProvider } from "@trymeka/ai-provider-vercel";
import { createAnchorBrowserComputerProvider } from "@trymeka/computer-provider-anchor-browser";
import { createAgent } from "@trymeka/core/ai/agent";
import { z } from "zod";

/**
* This example shows how to use the Anthropic model to run a task.
Expand Down Expand Up @@ -32,18 +31,19 @@ const agent = createAgent({
const session = await agent.initializeSession();
console.log("session live url", session.get()?.liveUrl);
const task = await session.runTask({
instructions: "Read the top article and summarize them",
instructions:
"Go through the show hacker news and identify what the top 7 posts have in common. Distill down to the most important 3 points and summarize them while using the original text as evidence. Also include the timing for the various posts.",
initialUrl: "https://news.ycombinator.com",
outputSchema: z.object({
articles: z.array(
z.object({
title: z.string(),
url: z.string(),
summary: z.string(),
author: z.string(),
}),
),
}),
// outputSchema: z.object({
// articles: z.array(
// z.object({
// title: z.string(),
// url: z.string(),
// summary: z.string(),
// author: z.string(),
// }),
// ),
// }),
});

console.log("Task", JSON.stringify(task.result, null, 2));
Expand Down
26 changes: 25 additions & 1 deletion packages/core/src/ai/agent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { z } from "zod";
import type { AIProvider, AgentLog, AgentMessage, Session, Task } from ".";
import { type Tool, createCompleteTaskTool, createWaitTool } from "../tools";
import {
SessionToDoListStore,
type Tool,
createCompleteTaskTool,
createToDoListTool,
createWaitTool,
} from "../tools";
import { type ComputerProvider, createComputerTool } from "../tools/computer";
import { ComputerProviderError, ToolCallError } from "../tools/errors";
import { SessionMemoryStore, createMemoryTool } from "../tools/memory";
Expand Down Expand Up @@ -174,6 +180,7 @@ export function createAgent<T, R>(options: {

// Create persistent memory store for this task
const memoryStore = new SessionMemoryStore();
const todoListStore = new SessionToDoListStore();

const coreTools = {
computer_action: createComputerTool({
Expand All @@ -199,6 +206,9 @@ export function createAgent<T, R>(options: {
wait: createWaitTool({
computerProvider,
}),
todo_list: createToDoListTool({
toDoListStore: todoListStore,
}),
};

// biome-ignore lint/suspicious/noExplicitAny: user defined
Expand Down Expand Up @@ -257,6 +267,20 @@ export function createAgent<T, R>(options: {
});
}

const taskListContext = todoListStore.getTaskListContext();
if (taskListContext) {
// Add task list context as the first user message so it's always visible
messages.unshift({
role: "user",
content: [
{
type: "text",
text: taskListContext,
},
],
});
}

return messages;
}

Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/ai/prompts/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ You have access to a persistent memory system that survives beyond the conversat
- Use of this tool is essential for any data that is related to the final outcome of the task.
- Actions: store (new), update (modify), retrieve (get), delete (remove), list (show keys)

## TASK LIST TOOL

You have access to a task list system to help you plan and execute your work:
- **task_list**: Create, manage, and track a list of tasks.
- Use this to break down complex goals into smaller, manageable steps.
- Before starting a complex task, create a plan using 'task_list' with the 'add' action.
- As you complete each step, update its status using the 'update' action (e.g., set to 'in-progress' or 'completed').
- Regularly review the task list with the 'list' action to stay on track.
- Actions: 'add' (new tasks), 'update' (modify status/description), 'list' (show all tasks)

This planning information helps maintain context and progress tracking across steps. You will see your previous planning context in the conversation marked with [PLANNING - Step X].

## TASK COMPLETION TOOL
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,10 @@ export {
createMemoryTool,
type MemoryStore,
} from "./memory";
export {
createToDoListTool,
SessionToDoListStore,
type ToDoListStore,
type ToDo,
} from "./todo-list";
export { ToolCallError, ComputerProviderError } from "./errors";
247 changes: 247 additions & 0 deletions packages/core/src/tools/todo-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
import z from "zod";
import type { Tool } from ".";
import { createAgentLogUpdate } from "../utils/agent-log";

const ToDoListToolSchema = z.discriminatedUnion("action", [
z.object({
action: z.literal("add"),
tasks: z
.array(
z.object({
description: z
.string()
.describe("The description of the task to add."),
}),
)
.describe("An array of tasks to add to the list."),
}),
z.object({
action: z.literal("update"),
tasks: z
.array(
z.object({
id: z.string().describe("The ID of the task to update."),
status: z
.enum(["pending", "in-progress", "completed", "cancelled"])
.describe("The new status of the task."),
description: z
.string()
.optional()
.describe("A new description for the task."),
}),
)
.describe("An array of tasks to update."),
}),
z.object({
action: z.literal("list"),
}),
]);

export interface ToDo {
id: string;
description: string;
status: "pending" | "in-progress" | "completed" | "cancelled";
}

export interface ToDoListStore {
get(id: string): ToDo | undefined | Promise<ToDo | undefined>;
add(tasks: { description: string }[]): ToDo[] | Promise<ToDo[]>;
update(
updates: {
id: string;
status?: "pending" | "in-progress" | "completed" | "cancelled";
description?: string | undefined;
}[],
): (ToDo | undefined)[] | Promise<(ToDo | undefined)[]>;
list(): ToDo[] | Promise<ToDo[]>;
clear(): void | Promise<void>;
}

export class SessionToDoListStore implements ToDoListStore {
private store = new Map<string, ToDo>();
private nextId = 1;

private generateId(): string {
return (this.nextId++).toString();
}

add(tasks: { description: string }[]): ToDo[] {
const newTasks: ToDo[] = [];
for (const task of tasks) {
const newTask: ToDo = {
id: this.generateId(),
description: task.description,
status: "pending",
};
this.store.set(newTask.id, newTask);
newTasks.push(newTask);
}
return newTasks;
}

update(
updates: {
id: string;
status?: "pending" | "in-progress" | "completed" | "cancelled";
description?: string;
}[],
): (ToDo | undefined)[] {
return updates.map((update) => {
const task = this.store.get(update.id);
if (task) {
if (update.status) {
task.status = update.status;
}
if (update.description) {
task.description = update.description;
}
this.store.set(task.id, task);
return task;
}
return undefined;
});
}

get(id: string): ToDo | undefined {
return this.store.get(id);
}

list(): ToDo[] {
return Array.from(this.store.values());
}

clear(): void {
this.store.clear();
}

// Get all tasks as formatted text for context injection
getTaskListContext(): string {
if (this.store.size === 0) {
return "";
}

const tasks = Array.from(this.store.values());
return `CURRENT TASK LIST:\n${tasks
.map((task) => `[${task.status}] ${task.id}: ${task.description}`)
.join("\n")}\n`;
}
}

export function createToDoListTool({
toDoListStore,
}: {
toDoListStore: ToDoListStore;
}): Tool<typeof ToDoListToolSchema, { result: string; success: boolean }> {
return {
description:
"Create, manage, and track a list of tasks to complete the user's request. Use this to break down complex tasks into smaller steps and track your progress.",
schema: ToDoListToolSchema,
execute: async (args, context) => {
try {
switch (args.action) {
case "add": {
const newTasks = await toDoListStore.add(args.tasks);
const response = {
role: "user" as const,
content: [
{
type: "text" as const,
text: `Successfully added ${
newTasks.length
} task(s). Here is the updated task list:
${(await toDoListStore.list())
.map((t) => `[${t.status}] ${t.id}: ${t.description}`)
.join("\n")}
Please proceed with the next step.`,
},
],
};
return {
type: "response",
response,
updateCurrentAgentLog: createAgentLogUpdate({
toolCallId: context.toolCallId,
toolName: "task_list",
args,
response,
}),
};
}
case "update": {
await toDoListStore.update(args.tasks);
const response = {
role: "user" as const,
content: [
{
type: "text" as const,
text: `Successfully updated task(s). Here is the updated task list:
${(await toDoListStore.list())
.map((t) => `[${t.status}] ${t.id}: ${t.description}`)
.join("\n")}
Please proceed with the next step.`,
},
],
};
return {
type: "response",
response,
updateCurrentAgentLog: createAgentLogUpdate({
toolCallId: context.toolCallId,
toolName: "task_list",
args,
response,
}),
};
}

case "list": {
const tasks = await toDoListStore.list();
const response = {
role: "user" as const,
content: [
{
type: "text" as const,
text:
tasks.length > 0
? `Current task list:\n${tasks
.map((t) => `[${t.status}] ${t.id}: ${t.description}`)
.join("\n")}\nPlease proceed with the next step.`
: "The task list is empty. Please add tasks to get started.",
},
],
};
return {
type: "response",
response,
updateCurrentAgentLog: createAgentLogUpdate({
toolCallId: context.toolCallId,
toolName: "task_list",
args,
response,
}),
};
}
default:
return {
type: "completion",
output: {
// @ts-expect-error - action is never
result: `Unknown action in task list tool: ${args.action}`,
success: false,
},
};
}
} catch (error) {
return {
type: "completion",
output: {
result: `Task list operation failed: ${
error instanceof Error ? error.message : String(error)
}`,
success: false,
},
};
}
},
};
}
Loading