@lupaflow/multi-agent 0.1.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/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@lupaflow/multi-agent",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "module": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "require": "./dist/index.js",
16
+ "import": "./dist/index.js"
17
+ }
18
+ },
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch"
22
+ },
23
+ "dependencies": {
24
+ "@lupaflow/core": "workspace:*",
25
+ "@lupaflow/types": "workspace:*",
26
+ "@lupaflow/agent": "workspace:*"
27
+ },
28
+ "devDependencies": {
29
+ "tsup": "^8.3.0",
30
+ "typescript": "^5.6.0"
31
+ }
32
+ }
@@ -0,0 +1,89 @@
1
+ import { generateId } from "@lupaflow/core"
2
+
3
+ export interface AgentMessage {
4
+ id: string
5
+ from: string
6
+ to: string
7
+ content: string
8
+ type: "request" | "response" | "broadcast"
9
+ timestamp: number
10
+ metadata?: Record<string, unknown>
11
+ }
12
+
13
+ export class AgentCommunicator {
14
+ private messages: AgentMessage[] = []
15
+ private listeners: Map<string, Set<(msg: AgentMessage) => void>> = new Map()
16
+
17
+ send(from: string, to: string, content: string, metadata?: Record<string, unknown>): AgentMessage {
18
+ const msg: AgentMessage = {
19
+ id: generateId(),
20
+ from,
21
+ to,
22
+ content,
23
+ type: "request",
24
+ timestamp: Date.now(),
25
+ metadata,
26
+ }
27
+ this.messages.push(msg)
28
+ const listeners = this.listeners.get(to)
29
+ if (listeners) {
30
+ for (const listener of listeners) {
31
+ listener(msg)
32
+ }
33
+ }
34
+ return msg
35
+ }
36
+
37
+ broadcast(from: string, content: string): void {
38
+ const msg: AgentMessage = {
39
+ id: generateId(),
40
+ from,
41
+ to: "*",
42
+ content,
43
+ type: "broadcast",
44
+ timestamp: Date.now(),
45
+ }
46
+ this.messages.push(msg)
47
+ for (const listeners of this.listeners.values()) {
48
+ for (const listener of listeners) {
49
+ listener(msg)
50
+ }
51
+ }
52
+ }
53
+
54
+ respond(to: string, originalId: string, content: string): AgentMessage {
55
+ const original = this.messages.find((m) => m.id === originalId)
56
+ const msg: AgentMessage = {
57
+ id: generateId(),
58
+ from: to,
59
+ to: original?.from || to,
60
+ content,
61
+ type: "response",
62
+ timestamp: Date.now(),
63
+ }
64
+ this.messages.push(msg)
65
+ const listeners = this.listeners.get(msg.to)
66
+ if (listeners) {
67
+ for (const listener of listeners) {
68
+ listener(msg)
69
+ }
70
+ }
71
+ return msg
72
+ }
73
+
74
+ on(agentId: string, handler: (msg: AgentMessage) => void): () => void {
75
+ if (!this.listeners.has(agentId)) {
76
+ this.listeners.set(agentId, new Set())
77
+ }
78
+ this.listeners.get(agentId)!.add(handler)
79
+ return () => this.listeners.get(agentId)?.delete(handler)
80
+ }
81
+
82
+ getHistory(): AgentMessage[] {
83
+ return [...this.messages]
84
+ }
85
+
86
+ clear(): void {
87
+ this.messages = []
88
+ }
89
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { Orchestrator } from "./orchestrator"
2
+ export { Team } from "./team"
3
+ export { AgentCommunicator } from "./communication"
4
+ export type { TeamMember, TeamConfig } from "./team"
5
+ export type { AgentMessage } from "./communication"
@@ -0,0 +1,89 @@
1
+ import { Agent } from "@lupaflow/agent"
2
+ import { AgentCommunicator, type AgentMessage } from "./communication"
3
+ import { Team, type TeamMember } from "./team"
4
+ import { generateId } from "@lupaflow/core"
5
+
6
+ export interface OrchestrationResult {
7
+ id: string
8
+ output: string
9
+ memberOutputs: Map<string, string>
10
+ messages: AgentMessage[]
11
+ durationMs: number
12
+ }
13
+
14
+ export class Orchestrator {
15
+ private team: Team
16
+ private communicator: AgentCommunicator
17
+ private agents: Map<string, Agent> = new Map()
18
+ private agentsReady: Map<string, Promise<void>> = new Map()
19
+
20
+ constructor(team: Team) {
21
+ this.team = team
22
+ this.communicator = new AgentCommunicator()
23
+ for (const member of team.config.members) {
24
+ const agent = new Agent(member.agentConfig)
25
+ this.agents.set(member.id, agent)
26
+ }
27
+ }
28
+
29
+ private async runMember(member: TeamMember, task: string): Promise<string> {
30
+ const agent = this.agents.get(member.id)!
31
+ const result = await agent.run(task)
32
+ return result.output
33
+ }
34
+
35
+ async run(task: string): Promise<OrchestrationResult> {
36
+ const start = Date.now()
37
+ const id = generateId()
38
+ const memberOutputs = new Map<string, string>()
39
+
40
+ const leader = this.team.getLeader()
41
+ if (!leader) throw new Error("No leader found in team")
42
+
43
+ const leaderResult = await this.runMember(leader, `As the ${leader.role}, oversee this task: ${task}`)
44
+ memberOutputs.set(leader.id, leaderResult)
45
+
46
+ const otherMembers = this.team.config.members.filter((m) => m.id !== leader.id)
47
+ for (const member of otherMembers) {
48
+ const msg = this.communicator.send(
49
+ leader.id,
50
+ member.id,
51
+ `Task: ${task}\nLeader's plan: ${leaderResult}\nYour role: ${member.role}\nExecute your part.`
52
+ )
53
+
54
+ const memberResult = await this.runMember(member, msg.content)
55
+ memberOutputs.set(member.id, memberResult)
56
+
57
+ this.communicator.respond(leader.id, msg.id, memberResult)
58
+ }
59
+
60
+ const allOutputs = Array.from(memberOutputs.values()).join("\n\n")
61
+ let finalOutput: string
62
+
63
+ if (otherMembers.length > 0) {
64
+ const finalResult = await this.runMember(
65
+ leader,
66
+ `Original task: ${task}\n\nHere are all the outputs from your team:\n${allOutputs}\n\nSynthesize a final cohesive response.`
67
+ )
68
+ finalOutput = finalResult
69
+ } else {
70
+ finalOutput = leaderResult
71
+ }
72
+
73
+ return {
74
+ id,
75
+ output: finalOutput,
76
+ memberOutputs,
77
+ messages: this.communicator.getHistory(),
78
+ durationMs: Date.now() - start,
79
+ }
80
+ }
81
+
82
+ getCommunicator(): AgentCommunicator {
83
+ return this.communicator
84
+ }
85
+
86
+ getTeam(): Team {
87
+ return this.team
88
+ }
89
+ }
package/src/team.ts ADDED
@@ -0,0 +1,40 @@
1
+ import type { AgentConfig } from "@lupaflow/types"
2
+ import { generateId } from "@lupaflow/core"
3
+
4
+ export interface TeamMember {
5
+ id: string
6
+ name: string
7
+ role: string
8
+ agentConfig: AgentConfig
9
+ }
10
+
11
+ export interface TeamConfig {
12
+ name: string
13
+ members: TeamMember[]
14
+ leader?: string
15
+ description?: string
16
+ }
17
+
18
+ export class Team {
19
+ public config: TeamConfig
20
+
21
+ constructor(config: TeamConfig) {
22
+ this.config = {
23
+ ...config,
24
+ members: config.members.map((m) => ({ ...m, id: m.id || generateId() })),
25
+ }
26
+ }
27
+
28
+ getMember(idOrName: string): TeamMember | undefined {
29
+ return this.config.members.find((m) => m.id === idOrName || m.name === idOrName)
30
+ }
31
+
32
+ getLeader(): TeamMember | undefined {
33
+ if (!this.config.leader) return this.config.members[0]
34
+ return this.getMember(this.config.leader)
35
+ }
36
+
37
+ getByRole(role: string): TeamMember[] {
38
+ return this.config.members.filter((m) => m.role === role)
39
+ }
40
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src"]
8
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "tsup"
2
+ export default defineConfig({
3
+ entry: { index: "src/index.ts" },
4
+ format: ["cjs", "esm"],
5
+ dts: true,
6
+ sourcemap: true,
7
+ clean: true,
8
+ external: [/node_modules/],
9
+ })