@jackchen_me/open-multi-agent 0.1.0 → 0.2.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 (84) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
  3. package/.github/pull_request_template.md +14 -0
  4. package/.github/workflows/ci.yml +23 -0
  5. package/CLAUDE.md +72 -0
  6. package/CODE_OF_CONDUCT.md +48 -0
  7. package/CONTRIBUTING.md +72 -0
  8. package/DECISIONS.md +43 -0
  9. package/README.md +73 -140
  10. package/README_zh.md +217 -0
  11. package/SECURITY.md +17 -0
  12. package/dist/agent/agent.d.ts +5 -0
  13. package/dist/agent/agent.d.ts.map +1 -1
  14. package/dist/agent/agent.js +90 -3
  15. package/dist/agent/agent.js.map +1 -1
  16. package/dist/agent/structured-output.d.ts +33 -0
  17. package/dist/agent/structured-output.d.ts.map +1 -0
  18. package/dist/agent/structured-output.js +116 -0
  19. package/dist/agent/structured-output.js.map +1 -0
  20. package/dist/index.d.ts +2 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +2 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/llm/adapter.d.ts +9 -4
  25. package/dist/llm/adapter.d.ts.map +1 -1
  26. package/dist/llm/adapter.js +17 -5
  27. package/dist/llm/adapter.js.map +1 -1
  28. package/dist/llm/anthropic.d.ts +1 -1
  29. package/dist/llm/anthropic.d.ts.map +1 -1
  30. package/dist/llm/anthropic.js +2 -1
  31. package/dist/llm/anthropic.js.map +1 -1
  32. package/dist/llm/copilot.d.ts +92 -0
  33. package/dist/llm/copilot.d.ts.map +1 -0
  34. package/dist/llm/copilot.js +426 -0
  35. package/dist/llm/copilot.js.map +1 -0
  36. package/dist/llm/openai-common.d.ts +47 -0
  37. package/dist/llm/openai-common.d.ts.map +1 -0
  38. package/dist/llm/openai-common.js +209 -0
  39. package/dist/llm/openai-common.js.map +1 -0
  40. package/dist/llm/openai.d.ts +1 -1
  41. package/dist/llm/openai.d.ts.map +1 -1
  42. package/dist/llm/openai.js +3 -224
  43. package/dist/llm/openai.js.map +1 -1
  44. package/dist/orchestrator/orchestrator.d.ts +25 -1
  45. package/dist/orchestrator/orchestrator.d.ts.map +1 -1
  46. package/dist/orchestrator/orchestrator.js +130 -37
  47. package/dist/orchestrator/orchestrator.js.map +1 -1
  48. package/dist/task/queue.js +1 -1
  49. package/dist/task/queue.js.map +1 -1
  50. package/dist/task/task.d.ts +3 -0
  51. package/dist/task/task.d.ts.map +1 -1
  52. package/dist/task/task.js +5 -1
  53. package/dist/task/task.js.map +1 -1
  54. package/dist/team/messaging.d.ts.map +1 -1
  55. package/dist/team/messaging.js +2 -1
  56. package/dist/team/messaging.js.map +1 -1
  57. package/dist/types.d.ts +31 -3
  58. package/dist/types.d.ts.map +1 -1
  59. package/examples/05-copilot-test.ts +49 -0
  60. package/examples/06-local-model.ts +199 -0
  61. package/examples/07-fan-out-aggregate.ts +209 -0
  62. package/examples/08-gemma4-local.ts +203 -0
  63. package/examples/09-gemma4-auto-orchestration.ts +162 -0
  64. package/package.json +4 -3
  65. package/src/agent/agent.ts +115 -6
  66. package/src/agent/structured-output.ts +126 -0
  67. package/src/index.ts +2 -1
  68. package/src/llm/adapter.ts +18 -5
  69. package/src/llm/anthropic.ts +2 -1
  70. package/src/llm/copilot.ts +551 -0
  71. package/src/llm/openai-common.ts +255 -0
  72. package/src/llm/openai.ts +8 -258
  73. package/src/orchestrator/orchestrator.ts +164 -38
  74. package/src/task/queue.ts +1 -1
  75. package/src/task/task.ts +8 -1
  76. package/src/team/messaging.ts +3 -1
  77. package/src/types.ts +31 -2
  78. package/tests/semaphore.test.ts +57 -0
  79. package/tests/shared-memory.test.ts +122 -0
  80. package/tests/structured-output.test.ts +331 -0
  81. package/tests/task-queue.test.ts +244 -0
  82. package/tests/task-retry.test.ts +368 -0
  83. package/tests/task-utils.test.ts +155 -0
  84. package/tests/tool-executor.test.ts +193 -0
@@ -0,0 +1,193 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { z } from 'zod'
3
+ import { ToolRegistry, defineTool } from '../src/tool/framework.js'
4
+ import { ToolExecutor } from '../src/tool/executor.js'
5
+ import type { ToolUseContext } from '../src/types.js'
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Helpers
9
+ // ---------------------------------------------------------------------------
10
+
11
+ const dummyContext: ToolUseContext = {
12
+ agent: { name: 'test-agent', role: 'tester', model: 'test-model' },
13
+ }
14
+
15
+ function echoTool() {
16
+ return defineTool({
17
+ name: 'echo',
18
+ description: 'Echoes the message.',
19
+ inputSchema: z.object({ message: z.string() }),
20
+ execute: async ({ message }) => ({ data: message, isError: false }),
21
+ })
22
+ }
23
+
24
+ function failTool() {
25
+ return defineTool({
26
+ name: 'fail',
27
+ description: 'Always throws.',
28
+ inputSchema: z.object({}),
29
+ execute: async () => {
30
+ throw new Error('intentional failure')
31
+ },
32
+ })
33
+ }
34
+
35
+ function makeExecutor(...tools: ReturnType<typeof defineTool>[]) {
36
+ const registry = new ToolRegistry()
37
+ for (const t of tools) registry.register(t)
38
+ return { executor: new ToolExecutor(registry), registry }
39
+ }
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Tests
43
+ // ---------------------------------------------------------------------------
44
+
45
+ describe('ToolExecutor', () => {
46
+ // -------------------------------------------------------------------------
47
+ // Single execution
48
+ // -------------------------------------------------------------------------
49
+
50
+ it('executes a tool and returns its result', async () => {
51
+ const { executor } = makeExecutor(echoTool())
52
+ const result = await executor.execute('echo', { message: 'hello' }, dummyContext)
53
+ expect(result.data).toBe('hello')
54
+ expect(result.isError).toBeFalsy()
55
+ })
56
+
57
+ it('returns an error result for an unknown tool', async () => {
58
+ const { executor } = makeExecutor()
59
+ const result = await executor.execute('ghost', {}, dummyContext)
60
+ expect(result.isError).toBe(true)
61
+ expect(result.data).toContain('not registered')
62
+ })
63
+
64
+ it('returns an error result when Zod validation fails', async () => {
65
+ const { executor } = makeExecutor(echoTool())
66
+ // 'message' is required but missing
67
+ const result = await executor.execute('echo', {}, dummyContext)
68
+ expect(result.isError).toBe(true)
69
+ expect(result.data).toContain('Invalid input')
70
+ })
71
+
72
+ it('catches tool execution errors and returns them as error results', async () => {
73
+ const { executor } = makeExecutor(failTool())
74
+ const result = await executor.execute('fail', {}, dummyContext)
75
+ expect(result.isError).toBe(true)
76
+ expect(result.data).toContain('intentional failure')
77
+ })
78
+
79
+ it('returns an error result when aborted before execution', async () => {
80
+ const { executor } = makeExecutor(echoTool())
81
+ const controller = new AbortController()
82
+ controller.abort()
83
+
84
+ const result = await executor.execute(
85
+ 'echo',
86
+ { message: 'hi' },
87
+ { ...dummyContext, abortSignal: controller.signal },
88
+ )
89
+ expect(result.isError).toBe(true)
90
+ expect(result.data).toContain('aborted')
91
+ })
92
+
93
+ // -------------------------------------------------------------------------
94
+ // Batch execution
95
+ // -------------------------------------------------------------------------
96
+
97
+ it('executeBatch runs multiple tools and returns a map of results', async () => {
98
+ const { executor } = makeExecutor(echoTool())
99
+ const results = await executor.executeBatch(
100
+ [
101
+ { id: 'c1', name: 'echo', input: { message: 'a' } },
102
+ { id: 'c2', name: 'echo', input: { message: 'b' } },
103
+ ],
104
+ dummyContext,
105
+ )
106
+
107
+ expect(results.size).toBe(2)
108
+ expect(results.get('c1')!.data).toBe('a')
109
+ expect(results.get('c2')!.data).toBe('b')
110
+ })
111
+
112
+ it('executeBatch isolates errors — one failure does not affect others', async () => {
113
+ const { executor } = makeExecutor(echoTool(), failTool())
114
+ const results = await executor.executeBatch(
115
+ [
116
+ { id: 'ok', name: 'echo', input: { message: 'fine' } },
117
+ { id: 'bad', name: 'fail', input: {} },
118
+ ],
119
+ dummyContext,
120
+ )
121
+
122
+ expect(results.get('ok')!.isError).toBeFalsy()
123
+ expect(results.get('bad')!.isError).toBe(true)
124
+ })
125
+
126
+ // -------------------------------------------------------------------------
127
+ // Concurrency control
128
+ // -------------------------------------------------------------------------
129
+
130
+ it('respects maxConcurrency limit', async () => {
131
+ let peak = 0
132
+ let running = 0
133
+
134
+ const trackTool = defineTool({
135
+ name: 'track',
136
+ description: 'Tracks concurrency.',
137
+ inputSchema: z.object({}),
138
+ execute: async () => {
139
+ running++
140
+ peak = Math.max(peak, running)
141
+ await new Promise((r) => setTimeout(r, 50))
142
+ running--
143
+ return { data: 'ok', isError: false }
144
+ },
145
+ })
146
+
147
+ const registry = new ToolRegistry()
148
+ registry.register(trackTool)
149
+ const executor = new ToolExecutor(registry, { maxConcurrency: 2 })
150
+
151
+ await executor.executeBatch(
152
+ Array.from({ length: 5 }, (_, i) => ({ id: `t${i}`, name: 'track', input: {} })),
153
+ dummyContext,
154
+ )
155
+
156
+ expect(peak).toBeLessThanOrEqual(2)
157
+ })
158
+ })
159
+
160
+ // ---------------------------------------------------------------------------
161
+ // ToolRegistry
162
+ // ---------------------------------------------------------------------------
163
+
164
+ describe('ToolRegistry', () => {
165
+ it('registers and retrieves a tool', () => {
166
+ const registry = new ToolRegistry()
167
+ registry.register(echoTool())
168
+ expect(registry.get('echo')).toBeDefined()
169
+ expect(registry.has('echo')).toBe(true)
170
+ })
171
+
172
+ it('throws on duplicate registration', () => {
173
+ const registry = new ToolRegistry()
174
+ registry.register(echoTool())
175
+ expect(() => registry.register(echoTool())).toThrow('already registered')
176
+ })
177
+
178
+ it('unregister removes the tool', () => {
179
+ const registry = new ToolRegistry()
180
+ registry.register(echoTool())
181
+ registry.unregister('echo')
182
+ expect(registry.has('echo')).toBe(false)
183
+ })
184
+
185
+ it('toToolDefs produces JSON schema representations', () => {
186
+ const registry = new ToolRegistry()
187
+ registry.register(echoTool())
188
+ const defs = registry.toToolDefs()
189
+ expect(defs).toHaveLength(1)
190
+ expect(defs[0].name).toBe('echo')
191
+ expect(defs[0].inputSchema).toHaveProperty('properties')
192
+ })
193
+ })