Skip to content
Merged
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
10 changes: 10 additions & 0 deletions agent-todo/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

# For inference
RESTACK_API_KEY=

# For Restack Cloud deployment
RESTACK_ENGINE_ID=
RESTACK_ENGINE_ADDRESS=
RESTACK_ENGINE_API_KEY=
RESTACK_ENGINE_API_ADDRESS=

Binary file added agent-todo/chat_post.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added agent-todo/chat_put.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added agent-todo/chat_run.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions agent-todo/eventAgent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { client } from "./src/client";

export type EventInput = {
agentId: string;
runId: string;
};

async function eventAgent(input: EventInput) {
try {
await client.sendAgentEvent({
event: {
name: "message",
input: { content: "Sales on boots?" },
},
agent: {
agentId: input.agentId,
runId: input.runId,
},
});

await client.sendAgentEvent({
event: {
name: "end",
},
agent: {
agentId: input.agentId,
runId: input.runId,
},
});

process.exit(0); // Exit the process successfully
} catch (error) {
console.error("Error sending event to agent:", error);
process.exit(1); // Exit the process with an error code
}
}

eventAgent({
agentId: "your-agent-id",
runId: "your-run-id",
});
26 changes: 26 additions & 0 deletions agent-todo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "agent-todo",
"version": "0.0.1",
"description": "Restack Agent managing todos",
"scripts": {
"dev": "open-cli http://localhost:5233 && tsx watch --include src src/services.ts",
"build": "tsc --build",
"clean": "rm -rf node_modules",
"schedule": "tsx scheduleAgent.ts",
"event": "tsx eventAgent.ts"
},
"dependencies": {
"@restackio/ai": "^0.0.104",
"@temporalio/workflow": "1.11.6",
"openai": "^4.80.1",
"zod": "^3.24.1"
},
"devDependencies": {
"@types/node": "20.16.9",
"dotenv-cli": "^7.4.2",
"open-cli": "^8.0.0",
"prettier": "3.3.3",
"tsx": "4.19.2",
"typescript": "^5.7.2"
}
}
104 changes: 104 additions & 0 deletions agent-todo/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Restack Agent Todos example

A sample repository with an agent managing todos.

## Requirements

- **Node 20+**

## Start Restack

To start Restack, use the following Docker command:

```bash
docker run -d --pull always --name restack -p 5233:5233 -p 6233:6233 -p 7233:7233 ghcr.io/restackio/restack:main
```

## Install dependencies and start services

```bash
npm install
npm run dev
```

This will start a Node.js app with Restack Services.
Your code will be running and syncing with Restack to execute agents.

## Run agents

### from UI

You can run agents from the UI by clicking the "Run" button.

![Run agents from UI](./chat_post.png)

### from API

You can run agents from the API by using the generated endpoint:

`POST http://localhost:6233/api/agents/agentTodo`

### from any client

You can run agents with any client connected to Restack, for example:

```bash
npm run schedule
```

executes `scheduleAgent.ts` which will connect to Restack and execute the `agentTodo` agent.

## Send events to the Agent

### from UI

You can send events like message or end from the UI.

![Send events from UI](./chat_put.png)

And see the events in the run:

![See events in UI](./chat_run.png)

### from API

You can send events to the agent by using the following endpoint:

`PUT http://localhost:6233/api/agents/agentTodo/:agentId/:runId`

with the payload:

```json
{
"eventName": "message",
"eventInput": { "content": "Create todo to send email to CEO" }
}
```

to send messages to the agent.

or

```json
{
"eventName": "end"
}
```

to end the conversation with the agent.

### from any client

You can send event to the agent with any client connected to Restack, for example:

Modify agentId and runId in eventAgent.ts and then run:

```bash
npm run event
```

It will connect to Restack and send 2 events to the agent, one to generate another agent and another one to end the conversation.

## Deploy on Restack Cloud

To deploy the application on Restack, you can create an account at [https://console.restack.io](https://console.restack.io)
29 changes: 29 additions & 0 deletions agent-todo/scheduleAgent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { client } from "./src/client";
import { agentChatTool } from "./src/agents/agent";
export type InputSchedule = {
name: string;
};

async function scheduleAgent(input: InputSchedule) {
try {
const agentId = `${Date.now()}-${agentChatTool.name}`;
const runId = await client.scheduleAgent({
agentName: agentChatTool.name,
agentId,
input,
});

const result = await client.getAgentResult({ agentId, runId });

console.log("Agent result:", result);

process.exit(0); // Exit the process successfully
} catch (error) {
console.error("Error scheduling agent:", error);
process.exit(1); // Exit the process with an error code
}
}

scheduleAgent({
name: "test",
});
103 changes: 103 additions & 0 deletions agent-todo/src/agents/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import {
defineEvent,
onEvent,
condition,
log,
step,
} from "@restackio/ai/agent";
import * as functions from "../functions";
import { childExecute } from "@restackio/ai/workflow";
import { executeTodoWorkflow } from "../workflows/executeTodo";

export type EndEvent = {
end: boolean;
};

export const messageEvent = defineEvent<functions.Message[]>("message");
export const endEvent = defineEvent("end");

type agentTodoOutput = {
messages: functions.Message[];
};

export async function agentTodo(): Promise<agentTodoOutput> {
let endReceived = false;
let messages: functions.Message[] = [];

const tools = await step<typeof functions>({}).getTools();

onEvent(messageEvent, async ({ content }: functions.Message) => {
messages.push({ role: "user", content: content?.toString() ?? "" });
const result = await step<typeof functions>({}).llmChat({
messages,
tools,
});

messages.push(result);

if (result.tool_calls) {
log.info("result.tool_calls", { result });
for (const toolCall of result.tool_calls) {
switch (toolCall.function.name) {
case "createTodo":
log.info("createTodo", { toolCall });
const toolResult = await step<typeof functions>({}).createTodo(
JSON.parse(toolCall.function.arguments)
);

messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: toolResult,
});

const toolChatResult = await step<typeof functions>({}).llmChat({
messages,
tools,
});

messages.push(toolChatResult);

break;
case "executeTodoWorkflow":
log.info("executeTodoWorkflow", { toolCall });
const workflowId = `executeTodoWorkflow-${new Date().getTime()}`;
const workflowResult = await childExecute({
child: executeTodoWorkflow,
childId: workflowId,
input: JSON.parse(toolCall.function.arguments),
});

messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: JSON.stringify(workflowResult),
});

const toolWorkflowResult = await step<typeof functions>({}).llmChat(
{
messages,
tools,
}
);

messages.push(toolWorkflowResult);

break;
default:
break;
}
}
}
return messages;
});

onEvent(endEvent, async () => {
endReceived = true;
});

await condition(() => endReceived);

log.info("end condition met");
return { messages };
}
1 change: 1 addition & 0 deletions agent-todo/src/agents/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./agent";
14 changes: 14 additions & 0 deletions agent-todo/src/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Restack from "@restackio/ai";

import "dotenv/config";

export const connectionOptions = {
engineId: process.env.RESTACK_ENGINE_ID!,
address: process.env.RESTACK_ENGINE_ADDRESS!,
apiKey: process.env.RESTACK_ENGINE_API_KEY!,
apiAddress: process.env.RESTACK_ENGINE_API_ADDRESS!,
};

export const client = new Restack(
process.env.RESTACK_ENGINE_API_KEY ? connectionOptions : undefined
);
7 changes: 7 additions & 0 deletions agent-todo/src/functions/createTodo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { log } from "@restackio/ai/function";

export const createTodo = async ({ todoTitle }: { todoTitle: string }) => {
const todo_id = `todo-${Math.floor(Math.random() * 10000)}`;
log.info("createTodo", { todo_id, todoTitle });
return `Created the todo '${todoTitle}' with id: ${todo_id}`;
};
7 changes: 7 additions & 0 deletions agent-todo/src/functions/getRandom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { log } from "@restackio/ai/function";

export const getRandom = async ({ todoTitle }: { todoTitle: string }) => {
const random = Math.random() * 100;
log.info("getRandom", { todoTitle, random });
return `The random number for ${todoTitle} is ${random}`;
};
17 changes: 17 additions & 0 deletions agent-todo/src/functions/getResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { log } from "@restackio/ai/function";

export const getResult = async ({
todoTitle,
todoId,
}: {
todoTitle: string;
todoId: string;
}) => {
const statuses = ["completed", "failed"];
const status = statuses[Math.floor(Math.random() * statuses.length)];
log.info("getResult", { todoId, todoTitle, status });
return {
todoId,
status,
};
};
18 changes: 18 additions & 0 deletions agent-todo/src/functions/getTools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { zodFunction } from "openai/helpers/zod";
import { executeTodoWorkflow } from "../workflows/executeTodo";
import { createTodo } from "./createTodo";
import { CreateTodoInput, ExecuteTodoInput } from "./toolTypes";

export const getTools = async () => {
const tools = [
zodFunction({
name: createTodo.name,
parameters: CreateTodoInput,
}),
zodFunction({
name: executeTodoWorkflow.name,
parameters: ExecuteTodoInput,
}),
];
return tools;
};
6 changes: 6 additions & 0 deletions agent-todo/src/functions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from "./llmChat";
export * from "./getTools";
export * from "./createTodo";
export * from "./getRandom";
export * from "./toolTypes";
export * from "./getResult";
Loading