@jackchen_me/open-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.
Files changed (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +280 -0
  3. package/dist/agent/agent.d.ts +121 -0
  4. package/dist/agent/agent.d.ts.map +1 -0
  5. package/dist/agent/agent.js +294 -0
  6. package/dist/agent/agent.js.map +1 -0
  7. package/dist/agent/pool.d.ts +128 -0
  8. package/dist/agent/pool.d.ts.map +1 -0
  9. package/dist/agent/pool.js +236 -0
  10. package/dist/agent/pool.js.map +1 -0
  11. package/dist/agent/runner.d.ts +120 -0
  12. package/dist/agent/runner.d.ts.map +1 -0
  13. package/dist/agent/runner.js +274 -0
  14. package/dist/agent/runner.js.map +1 -0
  15. package/dist/index.d.ts +73 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +87 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/llm/adapter.d.ts +38 -0
  20. package/dist/llm/adapter.d.ts.map +1 -0
  21. package/dist/llm/adapter.js +46 -0
  22. package/dist/llm/adapter.js.map +1 -0
  23. package/dist/llm/anthropic.d.ts +56 -0
  24. package/dist/llm/anthropic.d.ts.map +1 -0
  25. package/dist/llm/anthropic.js +307 -0
  26. package/dist/llm/anthropic.js.map +1 -0
  27. package/dist/llm/openai.d.ts +62 -0
  28. package/dist/llm/openai.d.ts.map +1 -0
  29. package/dist/llm/openai.js +424 -0
  30. package/dist/llm/openai.js.map +1 -0
  31. package/dist/memory/shared.d.ts +86 -0
  32. package/dist/memory/shared.d.ts.map +1 -0
  33. package/dist/memory/shared.js +155 -0
  34. package/dist/memory/shared.js.map +1 -0
  35. package/dist/memory/store.d.ts +64 -0
  36. package/dist/memory/store.d.ts.map +1 -0
  37. package/dist/memory/store.js +103 -0
  38. package/dist/memory/store.js.map +1 -0
  39. package/dist/orchestrator/orchestrator.d.ts +173 -0
  40. package/dist/orchestrator/orchestrator.d.ts.map +1 -0
  41. package/dist/orchestrator/orchestrator.js +698 -0
  42. package/dist/orchestrator/orchestrator.js.map +1 -0
  43. package/dist/orchestrator/scheduler.d.ts +112 -0
  44. package/dist/orchestrator/scheduler.d.ts.map +1 -0
  45. package/dist/orchestrator/scheduler.js +282 -0
  46. package/dist/orchestrator/scheduler.js.map +1 -0
  47. package/dist/task/queue.d.ts +160 -0
  48. package/dist/task/queue.d.ts.map +1 -0
  49. package/dist/task/queue.js +337 -0
  50. package/dist/task/queue.js.map +1 -0
  51. package/dist/task/task.d.ts +86 -0
  52. package/dist/task/task.d.ts.map +1 -0
  53. package/dist/task/task.js +201 -0
  54. package/dist/task/task.js.map +1 -0
  55. package/dist/team/messaging.d.ts +106 -0
  56. package/dist/team/messaging.d.ts.map +1 -0
  57. package/dist/team/messaging.js +182 -0
  58. package/dist/team/messaging.js.map +1 -0
  59. package/dist/team/team.d.ts +141 -0
  60. package/dist/team/team.d.ts.map +1 -0
  61. package/dist/team/team.js +282 -0
  62. package/dist/team/team.js.map +1 -0
  63. package/dist/tool/built-in/bash.d.ts +12 -0
  64. package/dist/tool/built-in/bash.d.ts.map +1 -0
  65. package/dist/tool/built-in/bash.js +133 -0
  66. package/dist/tool/built-in/bash.js.map +1 -0
  67. package/dist/tool/built-in/file-edit.d.ts +14 -0
  68. package/dist/tool/built-in/file-edit.d.ts.map +1 -0
  69. package/dist/tool/built-in/file-edit.js +130 -0
  70. package/dist/tool/built-in/file-edit.js.map +1 -0
  71. package/dist/tool/built-in/file-read.d.ts +12 -0
  72. package/dist/tool/built-in/file-read.d.ts.map +1 -0
  73. package/dist/tool/built-in/file-read.js +82 -0
  74. package/dist/tool/built-in/file-read.js.map +1 -0
  75. package/dist/tool/built-in/file-write.d.ts +11 -0
  76. package/dist/tool/built-in/file-write.d.ts.map +1 -0
  77. package/dist/tool/built-in/file-write.js +70 -0
  78. package/dist/tool/built-in/file-write.js.map +1 -0
  79. package/dist/tool/built-in/grep.d.ts +15 -0
  80. package/dist/tool/built-in/grep.d.ts.map +1 -0
  81. package/dist/tool/built-in/grep.js +287 -0
  82. package/dist/tool/built-in/grep.js.map +1 -0
  83. package/dist/tool/built-in/index.d.ts +36 -0
  84. package/dist/tool/built-in/index.d.ts.map +1 -0
  85. package/dist/tool/built-in/index.js +45 -0
  86. package/dist/tool/built-in/index.js.map +1 -0
  87. package/dist/tool/executor.d.ts +71 -0
  88. package/dist/tool/executor.d.ts.map +1 -0
  89. package/dist/tool/executor.js +116 -0
  90. package/dist/tool/executor.js.map +1 -0
  91. package/dist/tool/framework.d.ts +143 -0
  92. package/dist/tool/framework.d.ts.map +1 -0
  93. package/dist/tool/framework.js +371 -0
  94. package/dist/tool/framework.js.map +1 -0
  95. package/dist/types.d.ts +285 -0
  96. package/dist/types.d.ts.map +1 -0
  97. package/dist/types.js +8 -0
  98. package/dist/types.js.map +1 -0
  99. package/dist/utils/semaphore.d.ts +47 -0
  100. package/dist/utils/semaphore.d.ts.map +1 -0
  101. package/dist/utils/semaphore.js +85 -0
  102. package/dist/utils/semaphore.js.map +1 -0
  103. package/examples/01-single-agent.ts +131 -0
  104. package/examples/02-team-collaboration.ts +167 -0
  105. package/examples/03-task-pipeline.ts +201 -0
  106. package/examples/04-multi-model-team.ts +261 -0
  107. package/package.json +49 -0
  108. package/src/agent/agent.ts +364 -0
  109. package/src/agent/pool.ts +278 -0
  110. package/src/agent/runner.ts +413 -0
  111. package/src/index.ts +166 -0
  112. package/src/llm/adapter.ts +74 -0
  113. package/src/llm/anthropic.ts +388 -0
  114. package/src/llm/openai.ts +522 -0
  115. package/src/memory/shared.ts +181 -0
  116. package/src/memory/store.ts +124 -0
  117. package/src/orchestrator/orchestrator.ts +851 -0
  118. package/src/orchestrator/scheduler.ts +352 -0
  119. package/src/task/queue.ts +394 -0
  120. package/src/task/task.ts +232 -0
  121. package/src/team/messaging.ts +230 -0
  122. package/src/team/team.ts +334 -0
  123. package/src/tool/built-in/bash.ts +187 -0
  124. package/src/tool/built-in/file-edit.ts +154 -0
  125. package/src/tool/built-in/file-read.ts +105 -0
  126. package/src/tool/built-in/file-write.ts +81 -0
  127. package/src/tool/built-in/grep.ts +362 -0
  128. package/src/tool/built-in/index.ts +50 -0
  129. package/src/tool/executor.ts +178 -0
  130. package/src/tool/framework.ts +557 -0
  131. package/src/types.ts +362 -0
  132. package/src/utils/semaphore.ts +89 -0
  133. package/tsconfig.json +25 -0
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Example 02 — Multi-Agent Team Collaboration
3
+ *
4
+ * Three specialised agents (architect, developer, reviewer) collaborate on a
5
+ * shared goal. The OpenMultiAgent orchestrator breaks the goal into tasks, assigns
6
+ * them to the right agents, and collects the results.
7
+ *
8
+ * Run:
9
+ * npx tsx examples/02-team-collaboration.ts
10
+ *
11
+ * Prerequisites:
12
+ * ANTHROPIC_API_KEY env var must be set.
13
+ */
14
+
15
+ import { OpenMultiAgent } from '../src/index.js'
16
+ import type { AgentConfig, OrchestratorEvent } from '../src/types.js'
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Agent definitions
20
+ // ---------------------------------------------------------------------------
21
+
22
+ const architect: AgentConfig = {
23
+ name: 'architect',
24
+ model: 'claude-sonnet-4-6',
25
+ provider: 'anthropic',
26
+ systemPrompt: `You are a software architect with deep experience in Node.js and REST API design.
27
+ Your job is to design clear, production-quality API contracts and file/directory structures.
28
+ Output concise plans in markdown — no unnecessary prose.`,
29
+ tools: ['bash', 'file_write'],
30
+ maxTurns: 5,
31
+ temperature: 0.2,
32
+ }
33
+
34
+ const developer: AgentConfig = {
35
+ name: 'developer',
36
+ model: 'claude-sonnet-4-6',
37
+ provider: 'anthropic',
38
+ systemPrompt: `You are a TypeScript/Node.js developer. You implement what the architect specifies.
39
+ Write clean, runnable code with proper error handling. Use the tools to write files and run tests.`,
40
+ tools: ['bash', 'file_read', 'file_write', 'file_edit'],
41
+ maxTurns: 12,
42
+ temperature: 0.1,
43
+ }
44
+
45
+ const reviewer: AgentConfig = {
46
+ name: 'reviewer',
47
+ model: 'claude-sonnet-4-6',
48
+ provider: 'anthropic',
49
+ systemPrompt: `You are a senior code reviewer. Review code for correctness, security, and clarity.
50
+ Provide a structured review with: LGTM items, suggestions, and any blocking issues.
51
+ Read files using the tools before reviewing.`,
52
+ tools: ['bash', 'file_read', 'grep'],
53
+ maxTurns: 5,
54
+ temperature: 0.3,
55
+ }
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // Progress tracking
59
+ // ---------------------------------------------------------------------------
60
+
61
+ const startTimes = new Map<string, number>()
62
+
63
+ function handleProgress(event: OrchestratorEvent): void {
64
+ const ts = new Date().toISOString().slice(11, 23) // HH:MM:SS.mmm
65
+
66
+ switch (event.type) {
67
+ case 'agent_start':
68
+ startTimes.set(event.agent ?? '', Date.now())
69
+ console.log(`[${ts}] AGENT START → ${event.agent}`)
70
+ break
71
+
72
+ case 'agent_complete': {
73
+ const elapsed = Date.now() - (startTimes.get(event.agent ?? '') ?? Date.now())
74
+ console.log(`[${ts}] AGENT DONE ← ${event.agent} (${elapsed}ms)`)
75
+ break
76
+ }
77
+
78
+ case 'task_start':
79
+ console.log(`[${ts}] TASK START ↓ ${event.task}`)
80
+ break
81
+
82
+ case 'task_complete':
83
+ console.log(`[${ts}] TASK DONE ↑ ${event.task}`)
84
+ break
85
+
86
+ case 'message':
87
+ console.log(`[${ts}] MESSAGE • ${event.agent} → (team)`)
88
+ break
89
+
90
+ case 'error':
91
+ console.error(`[${ts}] ERROR ✗ agent=${event.agent} task=${event.task}`)
92
+ if (event.data instanceof Error) {
93
+ console.error(` ${event.data.message}`)
94
+ }
95
+ break
96
+ }
97
+ }
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // Orchestrate
101
+ // ---------------------------------------------------------------------------
102
+
103
+ const orchestrator = new OpenMultiAgent({
104
+ defaultModel: 'claude-sonnet-4-6',
105
+ maxConcurrency: 1, // run agents sequentially so output is readable
106
+ onProgress: handleProgress,
107
+ })
108
+
109
+ const team = orchestrator.createTeam('api-team', {
110
+ name: 'api-team',
111
+ agents: [architect, developer, reviewer],
112
+ sharedMemory: true,
113
+ maxConcurrency: 1,
114
+ })
115
+
116
+ console.log(`Team "${team.name}" created with agents: ${team.getAgents().map(a => a.name).join(', ')}`)
117
+ console.log('\nStarting team run...\n')
118
+ console.log('='.repeat(60))
119
+
120
+ const goal = `Create a minimal Express.js REST API in /tmp/express-api/ with:
121
+ - GET /health → { status: "ok" }
122
+ - GET /users → returns a hardcoded array of 2 user objects
123
+ - POST /users → accepts { name, email } body, logs it, returns 201
124
+ - Proper error handling middleware
125
+ - The server should listen on port 3001
126
+ - Include a package.json with the required dependencies`
127
+
128
+ const result = await orchestrator.runTeam(team, goal)
129
+
130
+ console.log('\n' + '='.repeat(60))
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // Results
134
+ // ---------------------------------------------------------------------------
135
+
136
+ console.log('\nTeam run complete.')
137
+ console.log(`Success: ${result.success}`)
138
+ console.log(`Total tokens — input: ${result.totalTokenUsage.input_tokens}, output: ${result.totalTokenUsage.output_tokens}`)
139
+
140
+ console.log('\nPer-agent results:')
141
+ for (const [agentName, agentResult] of result.agentResults) {
142
+ const status = agentResult.success ? 'OK' : 'FAILED'
143
+ const tools = agentResult.toolCalls.length
144
+ console.log(` ${agentName.padEnd(12)} [${status}] tool_calls=${tools}`)
145
+ if (!agentResult.success) {
146
+ console.log(` Error: ${agentResult.output.slice(0, 120)}`)
147
+ }
148
+ }
149
+
150
+ // Print the developer's final output (the actual code) as a sample
151
+ const developerResult = result.agentResults.get('developer')
152
+ if (developerResult?.success) {
153
+ console.log('\nDeveloper output (last 600 chars):')
154
+ console.log('─'.repeat(60))
155
+ const out = developerResult.output
156
+ console.log(out.length > 600 ? '...' + out.slice(-600) : out)
157
+ console.log('─'.repeat(60))
158
+ }
159
+
160
+ // Print the reviewer's findings
161
+ const reviewerResult = result.agentResults.get('reviewer')
162
+ if (reviewerResult?.success) {
163
+ console.log('\nReviewer output:')
164
+ console.log('─'.repeat(60))
165
+ console.log(reviewerResult.output)
166
+ console.log('─'.repeat(60))
167
+ }
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Example 03 — Explicit Task Pipeline with Dependencies
3
+ *
4
+ * Demonstrates how to define tasks with explicit dependency chains
5
+ * (design → implement → test → review) using runTasks(). The TaskQueue
6
+ * automatically blocks downstream tasks until their dependencies complete.
7
+ *
8
+ * Run:
9
+ * npx tsx examples/03-task-pipeline.ts
10
+ *
11
+ * Prerequisites:
12
+ * ANTHROPIC_API_KEY env var must be set.
13
+ */
14
+
15
+ import { OpenMultiAgent } from '../src/index.js'
16
+ import type { AgentConfig, OrchestratorEvent, Task } from '../src/types.js'
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Agents
20
+ // ---------------------------------------------------------------------------
21
+
22
+ const designer: AgentConfig = {
23
+ name: 'designer',
24
+ model: 'claude-sonnet-4-6',
25
+ systemPrompt: `You are a software designer. Your output is always a concise technical spec
26
+ in markdown. Focus on interfaces, data shapes, and file structure. Be brief.`,
27
+ tools: ['file_write'],
28
+ maxTurns: 4,
29
+ }
30
+
31
+ const implementer: AgentConfig = {
32
+ name: 'implementer',
33
+ model: 'claude-sonnet-4-6',
34
+ systemPrompt: `You are a TypeScript developer. Read the design spec written by the designer,
35
+ then implement it. Write all files to /tmp/pipeline-output/. Use the tools.`,
36
+ tools: ['bash', 'file_read', 'file_write'],
37
+ maxTurns: 10,
38
+ }
39
+
40
+ const tester: AgentConfig = {
41
+ name: 'tester',
42
+ model: 'claude-sonnet-4-6',
43
+ systemPrompt: `You are a QA engineer. Read the implemented files and run them to verify correctness.
44
+ Report: what passed, what failed, and any bugs found.`,
45
+ tools: ['bash', 'file_read', 'grep'],
46
+ maxTurns: 6,
47
+ }
48
+
49
+ const reviewer: AgentConfig = {
50
+ name: 'reviewer',
51
+ model: 'claude-sonnet-4-6',
52
+ systemPrompt: `You are a code reviewer. Read all files and produce a brief structured review.
53
+ Sections: Summary, Strengths, Issues (if any), Verdict (SHIP / NEEDS WORK).`,
54
+ tools: ['file_read', 'grep'],
55
+ maxTurns: 4,
56
+ }
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Progress handler — shows dependency blocking/unblocking
60
+ // ---------------------------------------------------------------------------
61
+
62
+ const taskTimes = new Map<string, number>()
63
+
64
+ function handleProgress(event: OrchestratorEvent): void {
65
+ const ts = new Date().toISOString().slice(11, 23)
66
+
67
+ switch (event.type) {
68
+ case 'task_start': {
69
+ taskTimes.set(event.task ?? '', Date.now())
70
+ const task = event.data as Task | undefined
71
+ console.log(`[${ts}] TASK READY "${task?.title ?? event.task}" (assignee: ${task?.assignee ?? 'any'})`)
72
+ break
73
+ }
74
+ case 'task_complete': {
75
+ const elapsed = Date.now() - (taskTimes.get(event.task ?? '') ?? Date.now())
76
+ const task = event.data as Task | undefined
77
+ console.log(`[${ts}] TASK DONE "${task?.title ?? event.task}" in ${elapsed}ms`)
78
+ break
79
+ }
80
+ case 'agent_start':
81
+ console.log(`[${ts}] AGENT START ${event.agent}`)
82
+ break
83
+ case 'agent_complete':
84
+ console.log(`[${ts}] AGENT DONE ${event.agent}`)
85
+ break
86
+ case 'error': {
87
+ const task = event.data as Task | undefined
88
+ console.error(`[${ts}] ERROR ${event.agent ?? ''} task="${task?.title ?? event.task}"`)
89
+ break
90
+ }
91
+ }
92
+ }
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // Build the pipeline
96
+ // ---------------------------------------------------------------------------
97
+
98
+ const orchestrator = new OpenMultiAgent({
99
+ defaultModel: 'claude-sonnet-4-6',
100
+ maxConcurrency: 2, // allow test + review to potentially run in parallel later
101
+ onProgress: handleProgress,
102
+ })
103
+
104
+ const team = orchestrator.createTeam('pipeline-team', {
105
+ name: 'pipeline-team',
106
+ agents: [designer, implementer, tester, reviewer],
107
+ sharedMemory: true,
108
+ })
109
+
110
+ // Task IDs — use stable strings so dependsOn can reference them
111
+ // (IDs will be generated by the framework; we capture the returned Task objects)
112
+ const SPEC_FILE = '/tmp/pipeline-output/design-spec.md'
113
+
114
+ const tasks: Array<{
115
+ title: string
116
+ description: string
117
+ assignee?: string
118
+ dependsOn?: string[]
119
+ }> = [
120
+ {
121
+ title: 'Design: URL shortener data model',
122
+ description: `Design a minimal in-memory URL shortener service.
123
+ Write a markdown spec to ${SPEC_FILE} covering:
124
+ - TypeScript interfaces for Url and ShortenRequest
125
+ - The shortening algorithm (hash approach is fine)
126
+ - API contract: POST /shorten, GET /:code
127
+ Keep the spec under 30 lines.`,
128
+ assignee: 'designer',
129
+ // no dependencies — this is the root task
130
+ },
131
+ {
132
+ title: 'Implement: URL shortener',
133
+ description: `Read the design spec at ${SPEC_FILE}.
134
+ Implement the URL shortener in /tmp/pipeline-output/src/:
135
+ - shortener.ts: core logic (shorten, resolve functions)
136
+ - server.ts: tiny HTTP server using Node's built-in http module (no Express)
137
+ - POST /shorten body: { url: string } → { code: string, short: string }
138
+ - GET /:code → redirect (301) or 404
139
+ - index.ts: entry point that starts the server on port 3002
140
+ No external dependencies beyond Node built-ins.`,
141
+ assignee: 'implementer',
142
+ dependsOn: ['Design: URL shortener data model'],
143
+ },
144
+ {
145
+ title: 'Test: URL shortener',
146
+ description: `Run the URL shortener implementation:
147
+ 1. Start the server: node /tmp/pipeline-output/src/index.ts (or tsx)
148
+ 2. POST a URL to shorten it using curl
149
+ 3. Verify the GET redirect works
150
+ 4. Report what passed and what (if anything) failed.
151
+ Kill the server after testing.`,
152
+ assignee: 'tester',
153
+ dependsOn: ['Implement: URL shortener'],
154
+ },
155
+ {
156
+ title: 'Review: URL shortener',
157
+ description: `Read all .ts files in /tmp/pipeline-output/src/ and the design spec.
158
+ Produce a structured code review with sections:
159
+ - Summary (2 sentences)
160
+ - Strengths (bullet list)
161
+ - Issues (bullet list, or "None" if clean)
162
+ - Verdict: SHIP or NEEDS WORK`,
163
+ assignee: 'reviewer',
164
+ dependsOn: ['Implement: URL shortener'], // runs in parallel with Test after Implement completes
165
+ },
166
+ ]
167
+
168
+ // ---------------------------------------------------------------------------
169
+ // Run
170
+ // ---------------------------------------------------------------------------
171
+
172
+ console.log('Starting 4-stage task pipeline...\n')
173
+ console.log('Pipeline: design → implement → test + review (parallel)')
174
+ console.log('='.repeat(60))
175
+
176
+ const result = await orchestrator.runTasks(team, tasks)
177
+
178
+ // ---------------------------------------------------------------------------
179
+ // Summary
180
+ // ---------------------------------------------------------------------------
181
+
182
+ console.log('\n' + '='.repeat(60))
183
+ console.log('Pipeline complete.\n')
184
+ console.log(`Overall success: ${result.success}`)
185
+ console.log(`Tokens — input: ${result.totalTokenUsage.input_tokens}, output: ${result.totalTokenUsage.output_tokens}`)
186
+
187
+ console.log('\nPer-agent summary:')
188
+ for (const [name, r] of result.agentResults) {
189
+ const icon = r.success ? 'OK ' : 'FAIL'
190
+ const toolCount = r.toolCalls.map(c => c.toolName).join(', ')
191
+ console.log(` [${icon}] ${name.padEnd(14)} tools used: ${toolCount || '(none)'}`)
192
+ }
193
+
194
+ // Print the reviewer's verdict
195
+ const review = result.agentResults.get('reviewer')
196
+ if (review?.success) {
197
+ console.log('\nCode review:')
198
+ console.log('─'.repeat(60))
199
+ console.log(review.output)
200
+ console.log('─'.repeat(60))
201
+ }
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Example 04 — Multi-Model Team with Custom Tools
3
+ *
4
+ * Demonstrates:
5
+ * - Mixing Anthropic and OpenAI models in the same team
6
+ * - Defining custom tools with defineTool() and Zod schemas
7
+ * - Building agents with a custom ToolRegistry so they can use custom tools
8
+ * - Running a team goal that uses the custom tools
9
+ *
10
+ * Run:
11
+ * npx tsx examples/04-multi-model-team.ts
12
+ *
13
+ * Prerequisites:
14
+ * ANTHROPIC_API_KEY and OPENAI_API_KEY env vars must be set.
15
+ * (If you only have one key, set useOpenAI = false below.)
16
+ */
17
+
18
+ import { z } from 'zod'
19
+ import { OpenMultiAgent, defineTool } from '../src/index.js'
20
+ import type { AgentConfig, OrchestratorEvent } from '../src/types.js'
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Custom tools — defined with defineTool() + Zod schemas
24
+ // ---------------------------------------------------------------------------
25
+
26
+ /**
27
+ * A custom tool that fetches live exchange rates from a public API.
28
+ */
29
+ const exchangeRateTool = defineTool({
30
+ name: 'get_exchange_rate',
31
+ description:
32
+ 'Get the current exchange rate between two currencies. ' +
33
+ 'Returns the rate as a decimal: 1 unit of `from` = N units of `to`.',
34
+ inputSchema: z.object({
35
+ from: z.string().describe('ISO 4217 currency code, e.g. "USD"'),
36
+ to: z.string().describe('ISO 4217 currency code, e.g. "EUR"'),
37
+ }),
38
+ execute: async ({ from, to }) => {
39
+ try {
40
+ const url = `https://api.exchangerate.host/convert?from=${from}&to=${to}&amount=1`
41
+ const resp = await fetch(url, { signal: AbortSignal.timeout(5000) })
42
+
43
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
44
+
45
+ interface ExchangeRateResponse {
46
+ result?: number
47
+ info?: { rate?: number }
48
+ }
49
+ const json = (await resp.json()) as ExchangeRateResponse
50
+ const rate: number | undefined = json?.result ?? json?.info?.rate
51
+
52
+ if (typeof rate !== 'number') throw new Error('Unexpected API response shape')
53
+
54
+ return {
55
+ data: JSON.stringify({ from, to, rate, timestamp: new Date().toISOString() }),
56
+ isError: false,
57
+ }
58
+ } catch (err) {
59
+ // Graceful degradation — return a stubbed rate so the team can still proceed
60
+ const stub = parseFloat((Math.random() * 0.5 + 0.8).toFixed(4))
61
+ return {
62
+ data: JSON.stringify({
63
+ from,
64
+ to,
65
+ rate: stub,
66
+ note: `Live fetch failed (${err instanceof Error ? err.message : String(err)}). Using stub rate.`,
67
+ }),
68
+ isError: false,
69
+ }
70
+ }
71
+ },
72
+ })
73
+
74
+ /**
75
+ * A custom tool that formats a number as a localised currency string.
76
+ */
77
+ const formatCurrencyTool = defineTool({
78
+ name: 'format_currency',
79
+ description: 'Format a number as a localised currency string.',
80
+ inputSchema: z.object({
81
+ amount: z.number().describe('The numeric amount to format.'),
82
+ currency: z.string().describe('ISO 4217 currency code, e.g. "USD".'),
83
+ locale: z
84
+ .string()
85
+ .optional()
86
+ .describe('BCP 47 locale string, e.g. "en-US". Defaults to "en-US".'),
87
+ }),
88
+ execute: async ({ amount, currency, locale = 'en-US' }) => {
89
+ try {
90
+ const formatted = new Intl.NumberFormat(locale, {
91
+ style: 'currency',
92
+ currency,
93
+ }).format(amount)
94
+ return { data: formatted, isError: false }
95
+ } catch {
96
+ return { data: `${amount} ${currency}`, isError: true }
97
+ }
98
+ },
99
+ })
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // Helper: build an AgentConfig whose tools list includes custom tool names.
103
+ //
104
+ // Agents reference tools by name in their AgentConfig.tools array.
105
+ // The ToolRegistry is injected via the Agent constructor. When using OpenMultiAgent
106
+ // convenience methods (runTeam, runTasks, runAgent), the orchestrator builds
107
+ // agents internally using buildAgent(), which registers only the five built-in
108
+ // tools. For custom tools, use AgentPool + Agent directly (see the note in the
109
+ // README) or provide the custom tool names in the tools array and rely on a
110
+ // registry you inject yourself.
111
+ //
112
+ // In this example we demonstrate the custom-tool pattern by running the agents
113
+ // directly through AgentPool rather than through the OpenMultiAgent high-level API.
114
+ // ---------------------------------------------------------------------------
115
+
116
+ import { Agent, AgentPool, ToolRegistry, ToolExecutor, registerBuiltInTools } from '../src/index.js'
117
+
118
+ /**
119
+ * Build an Agent with both built-in and custom tools registered.
120
+ */
121
+ function buildCustomAgent(
122
+ config: AgentConfig,
123
+ extraTools: ReturnType<typeof defineTool>[],
124
+ ): Agent {
125
+ const registry = new ToolRegistry()
126
+ registerBuiltInTools(registry)
127
+ for (const tool of extraTools) {
128
+ registry.register(tool)
129
+ }
130
+ const executor = new ToolExecutor(registry)
131
+ return new Agent(config, registry, executor)
132
+ }
133
+
134
+ // ---------------------------------------------------------------------------
135
+ // Agent definitions — mixed providers
136
+ // ---------------------------------------------------------------------------
137
+
138
+ const useOpenAI = Boolean(process.env.OPENAI_API_KEY)
139
+
140
+ const researcherConfig: AgentConfig = {
141
+ name: 'researcher',
142
+ model: 'claude-sonnet-4-6',
143
+ provider: 'anthropic',
144
+ systemPrompt: `You are a financial data researcher.
145
+ Use the get_exchange_rate tool to fetch current rates between the currency pairs you are given.
146
+ Return the raw rates as a JSON object keyed by pair, e.g. { "USD/EUR": 0.91, "USD/GBP": 0.79 }.`,
147
+ tools: ['get_exchange_rate'],
148
+ maxTurns: 6,
149
+ temperature: 0,
150
+ }
151
+
152
+ const analystConfig: AgentConfig = {
153
+ name: 'analyst',
154
+ model: useOpenAI ? 'gpt-5.4' : 'claude-sonnet-4-6',
155
+ provider: useOpenAI ? 'openai' : 'anthropic',
156
+ systemPrompt: `You are a foreign exchange analyst.
157
+ You receive exchange rate data and produce a short briefing.
158
+ Use format_currency to show example conversions.
159
+ Keep the briefing under 200 words.`,
160
+ tools: ['format_currency'],
161
+ maxTurns: 4,
162
+ temperature: 0.3,
163
+ }
164
+
165
+ // ---------------------------------------------------------------------------
166
+ // Build agents with custom tools
167
+ // ---------------------------------------------------------------------------
168
+
169
+ const researcher = buildCustomAgent(researcherConfig, [exchangeRateTool])
170
+ const analyst = buildCustomAgent(analystConfig, [formatCurrencyTool])
171
+
172
+ // ---------------------------------------------------------------------------
173
+ // Run with AgentPool for concurrency control
174
+ // ---------------------------------------------------------------------------
175
+
176
+ console.log('Multi-model team with custom tools')
177
+ console.log(`Providers: researcher=anthropic, analyst=${useOpenAI ? 'openai (gpt-5.4)' : 'anthropic (fallback)'}`)
178
+ console.log('Custom tools:', [exchangeRateTool.name, formatCurrencyTool.name].join(', '))
179
+ console.log()
180
+
181
+ const pool = new AgentPool(1) // sequential for readability
182
+ pool.add(researcher)
183
+ pool.add(analyst)
184
+
185
+ // Step 1: researcher fetches the rates
186
+ console.log('[1/2] Researcher fetching FX rates...')
187
+ const researchResult = await pool.run(
188
+ 'researcher',
189
+ `Fetch exchange rates for these pairs using the get_exchange_rate tool:
190
+ - USD to EUR
191
+ - USD to GBP
192
+ - USD to JPY
193
+ - EUR to GBP
194
+
195
+ Return the results as a JSON object: { "USD/EUR": <rate>, "USD/GBP": <rate>, ... }`,
196
+ )
197
+
198
+ if (!researchResult.success) {
199
+ console.error('Researcher failed:', researchResult.output)
200
+ process.exit(1)
201
+ }
202
+
203
+ console.log('Researcher done. Tool calls made:', researchResult.toolCalls.map(c => c.toolName).join(', '))
204
+
205
+ // Step 2: analyst writes the briefing, receiving the researcher output as context
206
+ console.log('\n[2/2] Analyst writing FX briefing...')
207
+ const analystResult = await pool.run(
208
+ 'analyst',
209
+ `Here are the current FX rates gathered by the research team:
210
+
211
+ ${researchResult.output}
212
+
213
+ Using format_currency, show what $1,000 USD and €1,000 EUR convert to in each of the other currencies.
214
+ Then write a short FX market briefing (under 200 words) covering:
215
+ - Each rate with a brief observation
216
+ - The strongest and weakest currency in the set
217
+ - One-sentence market comment`,
218
+ )
219
+
220
+ if (!analystResult.success) {
221
+ console.error('Analyst failed:', analystResult.output)
222
+ process.exit(1)
223
+ }
224
+
225
+ console.log('Analyst done. Tool calls made:', analystResult.toolCalls.map(c => c.toolName).join(', '))
226
+
227
+ // ---------------------------------------------------------------------------
228
+ // Results
229
+ // ---------------------------------------------------------------------------
230
+
231
+ console.log('\n' + '='.repeat(60))
232
+
233
+ console.log('\nResearcher output:')
234
+ console.log(researchResult.output.slice(0, 400))
235
+
236
+ console.log('\nAnalyst briefing:')
237
+ console.log('─'.repeat(60))
238
+ console.log(analystResult.output)
239
+ console.log('─'.repeat(60))
240
+
241
+ const totalInput = researchResult.tokenUsage.input_tokens + analystResult.tokenUsage.input_tokens
242
+ const totalOutput = researchResult.tokenUsage.output_tokens + analystResult.tokenUsage.output_tokens
243
+ console.log(`\nTotal tokens — input: ${totalInput}, output: ${totalOutput}`)
244
+
245
+ // ---------------------------------------------------------------------------
246
+ // Bonus: show how defineTool() works in isolation (no LLM needed)
247
+ // ---------------------------------------------------------------------------
248
+
249
+ console.log('\n--- Bonus: testing custom tools in isolation ---\n')
250
+
251
+ const fmtResult = await formatCurrencyTool.execute(
252
+ { amount: 1234.56, currency: 'EUR', locale: 'de-DE' },
253
+ { agent: { name: 'test', role: 'test', model: 'test' } },
254
+ )
255
+ console.log(`format_currency(1234.56, EUR, de-DE) = ${fmtResult.data}`)
256
+
257
+ const rateResult = await exchangeRateTool.execute(
258
+ { from: 'USD', to: 'EUR' },
259
+ { agent: { name: 'test', role: 'test', model: 'test' } },
260
+ )
261
+ console.log(`get_exchange_rate(USD→EUR) = ${rateResult.data}`)
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@jackchen_me/open-multi-agent",
3
+ "version": "0.1.0",
4
+ "description": "Production-grade multi-agent orchestration framework. Model-agnostic, supports team collaboration, task scheduling, and inter-agent communication.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "dev": "tsc --watch",
17
+ "test": "vitest run",
18
+ "test:watch": "vitest",
19
+ "lint": "tsc --noEmit",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "keywords": [
23
+ "ai",
24
+ "agent",
25
+ "multi-agent",
26
+ "orchestration",
27
+ "llm",
28
+ "claude",
29
+ "openai",
30
+ "mcp",
31
+ "tool-use",
32
+ "agent-framework"
33
+ ],
34
+ "author": "",
35
+ "license": "MIT",
36
+ "engines": {
37
+ "node": ">=18.0.0"
38
+ },
39
+ "dependencies": {
40
+ "@anthropic-ai/sdk": "^0.52.0",
41
+ "openai": "^4.73.0",
42
+ "zod": "^3.23.0"
43
+ },
44
+ "devDependencies": {
45
+ "typescript": "^5.6.0",
46
+ "vitest": "^2.1.0",
47
+ "@types/node": "^22.0.0"
48
+ }
49
+ }