@jackchen_me/open-multi-agent 1.0.0 → 1.0.1

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 (80) hide show
  1. package/package.json +8 -2
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -23
  4. package/.github/pull_request_template.md +0 -14
  5. package/.github/workflows/ci.yml +0 -23
  6. package/CLAUDE.md +0 -80
  7. package/CODE_OF_CONDUCT.md +0 -48
  8. package/CONTRIBUTING.md +0 -72
  9. package/DECISIONS.md +0 -43
  10. package/README_zh.md +0 -277
  11. package/SECURITY.md +0 -17
  12. package/examples/01-single-agent.ts +0 -131
  13. package/examples/02-team-collaboration.ts +0 -167
  14. package/examples/03-task-pipeline.ts +0 -201
  15. package/examples/04-multi-model-team.ts +0 -261
  16. package/examples/05-copilot-test.ts +0 -49
  17. package/examples/06-local-model.ts +0 -200
  18. package/examples/07-fan-out-aggregate.ts +0 -209
  19. package/examples/08-gemma4-local.ts +0 -192
  20. package/examples/09-structured-output.ts +0 -73
  21. package/examples/10-task-retry.ts +0 -132
  22. package/examples/11-trace-observability.ts +0 -133
  23. package/examples/12-grok.ts +0 -154
  24. package/examples/13-gemini.ts +0 -48
  25. package/src/agent/agent.ts +0 -622
  26. package/src/agent/loop-detector.ts +0 -137
  27. package/src/agent/pool.ts +0 -285
  28. package/src/agent/runner.ts +0 -542
  29. package/src/agent/structured-output.ts +0 -126
  30. package/src/index.ts +0 -182
  31. package/src/llm/adapter.ts +0 -98
  32. package/src/llm/anthropic.ts +0 -389
  33. package/src/llm/copilot.ts +0 -552
  34. package/src/llm/gemini.ts +0 -378
  35. package/src/llm/grok.ts +0 -29
  36. package/src/llm/openai-common.ts +0 -294
  37. package/src/llm/openai.ts +0 -292
  38. package/src/memory/shared.ts +0 -181
  39. package/src/memory/store.ts +0 -124
  40. package/src/orchestrator/orchestrator.ts +0 -1071
  41. package/src/orchestrator/scheduler.ts +0 -352
  42. package/src/task/queue.ts +0 -464
  43. package/src/task/task.ts +0 -239
  44. package/src/team/messaging.ts +0 -232
  45. package/src/team/team.ts +0 -334
  46. package/src/tool/built-in/bash.ts +0 -187
  47. package/src/tool/built-in/file-edit.ts +0 -154
  48. package/src/tool/built-in/file-read.ts +0 -105
  49. package/src/tool/built-in/file-write.ts +0 -81
  50. package/src/tool/built-in/grep.ts +0 -362
  51. package/src/tool/built-in/index.ts +0 -50
  52. package/src/tool/executor.ts +0 -178
  53. package/src/tool/framework.ts +0 -557
  54. package/src/tool/text-tool-extractor.ts +0 -219
  55. package/src/types.ts +0 -542
  56. package/src/utils/semaphore.ts +0 -89
  57. package/src/utils/trace.ts +0 -34
  58. package/tests/agent-hooks.test.ts +0 -473
  59. package/tests/agent-pool.test.ts +0 -212
  60. package/tests/approval.test.ts +0 -464
  61. package/tests/built-in-tools.test.ts +0 -393
  62. package/tests/gemini-adapter.test.ts +0 -97
  63. package/tests/grok-adapter.test.ts +0 -74
  64. package/tests/llm-adapters.test.ts +0 -357
  65. package/tests/loop-detection.test.ts +0 -456
  66. package/tests/openai-fallback.test.ts +0 -159
  67. package/tests/orchestrator.test.ts +0 -281
  68. package/tests/scheduler.test.ts +0 -221
  69. package/tests/semaphore.test.ts +0 -57
  70. package/tests/shared-memory.test.ts +0 -122
  71. package/tests/structured-output.test.ts +0 -331
  72. package/tests/task-queue.test.ts +0 -244
  73. package/tests/task-retry.test.ts +0 -368
  74. package/tests/task-utils.test.ts +0 -155
  75. package/tests/team-messaging.test.ts +0 -329
  76. package/tests/text-tool-extractor.test.ts +0 -170
  77. package/tests/tool-executor.test.ts +0 -193
  78. package/tests/trace.test.ts +0 -453
  79. package/tsconfig.json +0 -25
  80. package/vitest.config.ts +0 -9
@@ -1,453 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest'
2
- import { z } from 'zod'
3
- import { Agent } from '../src/agent/agent.js'
4
- import { AgentRunner, type RunOptions } from '../src/agent/runner.js'
5
- import { ToolRegistry, defineTool } from '../src/tool/framework.js'
6
- import { ToolExecutor } from '../src/tool/executor.js'
7
- import { executeWithRetry } from '../src/orchestrator/orchestrator.js'
8
- import { emitTrace, generateRunId } from '../src/utils/trace.js'
9
- import { createTask } from '../src/task/task.js'
10
- import type {
11
- AgentConfig,
12
- AgentRunResult,
13
- LLMAdapter,
14
- LLMResponse,
15
- TraceEvent,
16
- } from '../src/types.js'
17
-
18
- // ---------------------------------------------------------------------------
19
- // Mock adapters
20
- // ---------------------------------------------------------------------------
21
-
22
- function mockAdapter(responses: LLMResponse[]): LLMAdapter {
23
- let callIndex = 0
24
- return {
25
- name: 'mock',
26
- async chat() {
27
- return responses[callIndex++]!
28
- },
29
- async *stream() {
30
- /* unused */
31
- },
32
- }
33
- }
34
-
35
- function textResponse(text: string): LLMResponse {
36
- return {
37
- id: `resp-${Math.random().toString(36).slice(2)}`,
38
- content: [{ type: 'text' as const, text }],
39
- model: 'mock-model',
40
- stop_reason: 'end_turn',
41
- usage: { input_tokens: 10, output_tokens: 20 },
42
- }
43
- }
44
-
45
- function toolUseResponse(toolName: string, input: Record<string, unknown>): LLMResponse {
46
- return {
47
- id: `resp-${Math.random().toString(36).slice(2)}`,
48
- content: [
49
- {
50
- type: 'tool_use' as const,
51
- id: `tu-${Math.random().toString(36).slice(2)}`,
52
- name: toolName,
53
- input,
54
- },
55
- ],
56
- model: 'mock-model',
57
- stop_reason: 'tool_use',
58
- usage: { input_tokens: 15, output_tokens: 25 },
59
- }
60
- }
61
-
62
- function buildMockAgent(
63
- config: AgentConfig,
64
- responses: LLMResponse[],
65
- registry?: ToolRegistry,
66
- executor?: ToolExecutor,
67
- ): Agent {
68
- const reg = registry ?? new ToolRegistry()
69
- const exec = executor ?? new ToolExecutor(reg)
70
- const adapter = mockAdapter(responses)
71
- const agent = new Agent(config, reg, exec)
72
-
73
- const runner = new AgentRunner(adapter, reg, exec, {
74
- model: config.model,
75
- systemPrompt: config.systemPrompt,
76
- maxTurns: config.maxTurns,
77
- maxTokens: config.maxTokens,
78
- temperature: config.temperature,
79
- agentName: config.name,
80
- })
81
- ;(agent as any).runner = runner
82
-
83
- return agent
84
- }
85
-
86
- // ---------------------------------------------------------------------------
87
- // emitTrace helper
88
- // ---------------------------------------------------------------------------
89
-
90
- describe('emitTrace', () => {
91
- it('does nothing when fn is undefined', () => {
92
- // Should not throw
93
- emitTrace(undefined, {
94
- type: 'agent',
95
- runId: 'r1',
96
- agent: 'a',
97
- turns: 1,
98
- tokens: { input_tokens: 0, output_tokens: 0 },
99
- toolCalls: 0,
100
- startMs: 0,
101
- endMs: 0,
102
- durationMs: 0,
103
- })
104
- })
105
-
106
- it('calls fn with the event', () => {
107
- const fn = vi.fn()
108
- const event: TraceEvent = {
109
- type: 'agent',
110
- runId: 'r1',
111
- agent: 'a',
112
- turns: 1,
113
- tokens: { input_tokens: 0, output_tokens: 0 },
114
- toolCalls: 0,
115
- startMs: 0,
116
- endMs: 0,
117
- durationMs: 0,
118
- }
119
- emitTrace(fn, event)
120
- expect(fn).toHaveBeenCalledWith(event)
121
- })
122
-
123
- it('swallows errors thrown by callback', () => {
124
- const fn = () => { throw new Error('boom') }
125
- expect(() =>
126
- emitTrace(fn, {
127
- type: 'agent',
128
- runId: 'r1',
129
- agent: 'a',
130
- turns: 1,
131
- tokens: { input_tokens: 0, output_tokens: 0 },
132
- toolCalls: 0,
133
- startMs: 0,
134
- endMs: 0,
135
- durationMs: 0,
136
- }),
137
- ).not.toThrow()
138
- })
139
-
140
- it('swallows rejected promises from async callbacks', async () => {
141
- // An async onTrace that rejects should not produce unhandled rejection
142
- const fn = async () => { throw new Error('async boom') }
143
- emitTrace(fn as unknown as (event: TraceEvent) => void, {
144
- type: 'agent',
145
- runId: 'r1',
146
- agent: 'a',
147
- turns: 1,
148
- tokens: { input_tokens: 0, output_tokens: 0 },
149
- toolCalls: 0,
150
- startMs: 0,
151
- endMs: 0,
152
- durationMs: 0,
153
- })
154
- // If the rejection is not caught, vitest will fail with unhandled rejection.
155
- // Give the microtask queue a tick to surface any unhandled rejection.
156
- await new Promise(resolve => setTimeout(resolve, 10))
157
- })
158
- })
159
-
160
- describe('generateRunId', () => {
161
- it('returns a UUID string', () => {
162
- const id = generateRunId()
163
- expect(id).toMatch(/^[0-9a-f-]{36}$/)
164
- })
165
-
166
- it('returns unique IDs', () => {
167
- const ids = new Set(Array.from({ length: 100 }, generateRunId))
168
- expect(ids.size).toBe(100)
169
- })
170
- })
171
-
172
- // ---------------------------------------------------------------------------
173
- // AgentRunner trace events
174
- // ---------------------------------------------------------------------------
175
-
176
- describe('AgentRunner trace events', () => {
177
- it('emits llm_call trace for each LLM turn', async () => {
178
- const traces: TraceEvent[] = []
179
- const registry = new ToolRegistry()
180
- const executor = new ToolExecutor(registry)
181
- const adapter = mockAdapter([textResponse('Hello!')])
182
-
183
- const runner = new AgentRunner(adapter, registry, executor, {
184
- model: 'test-model',
185
- agentName: 'test-agent',
186
- })
187
-
188
- const runOptions: RunOptions = {
189
- onTrace: (e) => traces.push(e),
190
- runId: 'run-1',
191
- traceAgent: 'test-agent',
192
- }
193
-
194
- await runner.run(
195
- [{ role: 'user', content: [{ type: 'text', text: 'hi' }] }],
196
- runOptions,
197
- )
198
-
199
- const llmTraces = traces.filter(t => t.type === 'llm_call')
200
- expect(llmTraces).toHaveLength(1)
201
-
202
- const llm = llmTraces[0]!
203
- expect(llm.type).toBe('llm_call')
204
- expect(llm.runId).toBe('run-1')
205
- expect(llm.agent).toBe('test-agent')
206
- expect(llm.model).toBe('test-model')
207
- expect(llm.turn).toBe(1)
208
- expect(llm.tokens).toEqual({ input_tokens: 10, output_tokens: 20 })
209
- expect(llm.durationMs).toBeGreaterThanOrEqual(0)
210
- expect(llm.startMs).toBeLessThanOrEqual(llm.endMs)
211
- })
212
-
213
- it('emits tool_call trace with correct fields', async () => {
214
- const traces: TraceEvent[] = []
215
- const registry = new ToolRegistry()
216
- registry.register(
217
- defineTool({
218
- name: 'echo',
219
- description: 'echoes',
220
- inputSchema: z.object({ msg: z.string() }),
221
- execute: async ({ msg }) => ({ data: msg }),
222
- }),
223
- )
224
- const executor = new ToolExecutor(registry)
225
- const adapter = mockAdapter([
226
- toolUseResponse('echo', { msg: 'hello' }),
227
- textResponse('Done'),
228
- ])
229
-
230
- const runner = new AgentRunner(adapter, registry, executor, {
231
- model: 'test-model',
232
- agentName: 'tooler',
233
- })
234
-
235
- await runner.run(
236
- [{ role: 'user', content: [{ type: 'text', text: 'test' }] }],
237
- { onTrace: (e) => traces.push(e), runId: 'run-2', traceAgent: 'tooler' },
238
- )
239
-
240
- const toolTraces = traces.filter(t => t.type === 'tool_call')
241
- expect(toolTraces).toHaveLength(1)
242
-
243
- const tool = toolTraces[0]!
244
- expect(tool.type).toBe('tool_call')
245
- expect(tool.runId).toBe('run-2')
246
- expect(tool.agent).toBe('tooler')
247
- expect(tool.tool).toBe('echo')
248
- expect(tool.isError).toBe(false)
249
- expect(tool.durationMs).toBeGreaterThanOrEqual(0)
250
- })
251
-
252
- it('tool_call trace has isError: true on tool failure', async () => {
253
- const traces: TraceEvent[] = []
254
- const registry = new ToolRegistry()
255
- registry.register(
256
- defineTool({
257
- name: 'boom',
258
- description: 'fails',
259
- inputSchema: z.object({}),
260
- execute: async () => { throw new Error('fail') },
261
- }),
262
- )
263
- const executor = new ToolExecutor(registry)
264
- const adapter = mockAdapter([
265
- toolUseResponse('boom', {}),
266
- textResponse('Handled'),
267
- ])
268
-
269
- const runner = new AgentRunner(adapter, registry, executor, {
270
- model: 'test-model',
271
- agentName: 'err-agent',
272
- })
273
-
274
- await runner.run(
275
- [{ role: 'user', content: [{ type: 'text', text: 'test' }] }],
276
- { onTrace: (e) => traces.push(e), runId: 'run-3', traceAgent: 'err-agent' },
277
- )
278
-
279
- const toolTraces = traces.filter(t => t.type === 'tool_call')
280
- expect(toolTraces).toHaveLength(1)
281
- expect(toolTraces[0]!.isError).toBe(true)
282
- })
283
-
284
- it('does not call Date.now for LLM timing when onTrace is absent', async () => {
285
- // This test just verifies no errors occur when onTrace is not provided
286
- const registry = new ToolRegistry()
287
- const executor = new ToolExecutor(registry)
288
- const adapter = mockAdapter([textResponse('hi')])
289
-
290
- const runner = new AgentRunner(adapter, registry, executor, {
291
- model: 'test-model',
292
- })
293
-
294
- const result = await runner.run(
295
- [{ role: 'user', content: [{ type: 'text', text: 'test' }] }],
296
- {},
297
- )
298
-
299
- expect(result.output).toBe('hi')
300
- })
301
- })
302
-
303
- // ---------------------------------------------------------------------------
304
- // Agent-level trace events
305
- // ---------------------------------------------------------------------------
306
-
307
- describe('Agent trace events', () => {
308
- it('emits agent trace with turns, tokens, and toolCalls', async () => {
309
- const traces: TraceEvent[] = []
310
- const config: AgentConfig = {
311
- name: 'my-agent',
312
- model: 'mock-model',
313
- systemPrompt: 'You are a test.',
314
- }
315
-
316
- const agent = buildMockAgent(config, [textResponse('Hello world')])
317
-
318
- const runOptions: Partial<RunOptions> = {
319
- onTrace: (e) => traces.push(e),
320
- runId: 'run-agent-1',
321
- traceAgent: 'my-agent',
322
- }
323
-
324
- const result = await agent.run('Say hello', runOptions)
325
- expect(result.success).toBe(true)
326
-
327
- const agentTraces = traces.filter(t => t.type === 'agent')
328
- expect(agentTraces).toHaveLength(1)
329
-
330
- const at = agentTraces[0]!
331
- expect(at.type).toBe('agent')
332
- expect(at.runId).toBe('run-agent-1')
333
- expect(at.agent).toBe('my-agent')
334
- expect(at.turns).toBe(1) // one assistant message
335
- expect(at.tokens).toEqual({ input_tokens: 10, output_tokens: 20 })
336
- expect(at.toolCalls).toBe(0)
337
- expect(at.durationMs).toBeGreaterThanOrEqual(0)
338
- })
339
-
340
- it('all traces share the same runId', async () => {
341
- const traces: TraceEvent[] = []
342
- const registry = new ToolRegistry()
343
- registry.register(
344
- defineTool({
345
- name: 'greet',
346
- description: 'greets',
347
- inputSchema: z.object({ name: z.string() }),
348
- execute: async ({ name }) => ({ data: `Hi ${name}` }),
349
- }),
350
- )
351
- const executor = new ToolExecutor(registry)
352
- const config: AgentConfig = {
353
- name: 'multi-trace-agent',
354
- model: 'mock-model',
355
- tools: ['greet'],
356
- }
357
-
358
- const agent = buildMockAgent(
359
- config,
360
- [
361
- toolUseResponse('greet', { name: 'world' }),
362
- textResponse('Done'),
363
- ],
364
- registry,
365
- executor,
366
- )
367
-
368
- const runId = 'shared-run-id'
369
- await agent.run('test', {
370
- onTrace: (e) => traces.push(e),
371
- runId,
372
- traceAgent: 'multi-trace-agent',
373
- })
374
-
375
- // Should have: 2 llm_call, 1 tool_call, 1 agent
376
- expect(traces.length).toBeGreaterThanOrEqual(4)
377
-
378
- for (const trace of traces) {
379
- expect(trace.runId).toBe(runId)
380
- }
381
- })
382
-
383
- it('onTrace error does not break agent execution', async () => {
384
- const config: AgentConfig = {
385
- name: 'resilient-agent',
386
- model: 'mock-model',
387
- }
388
-
389
- const agent = buildMockAgent(config, [textResponse('OK')])
390
-
391
- const result = await agent.run('test', {
392
- onTrace: () => { throw new Error('callback exploded') },
393
- runId: 'run-err',
394
- traceAgent: 'resilient-agent',
395
- })
396
-
397
- // The run should still succeed despite the broken callback
398
- expect(result.success).toBe(true)
399
- expect(result.output).toBe('OK')
400
- })
401
-
402
- it('per-turn token usage in llm_call traces', async () => {
403
- const traces: TraceEvent[] = []
404
- const registry = new ToolRegistry()
405
- registry.register(
406
- defineTool({
407
- name: 'noop',
408
- description: 'noop',
409
- inputSchema: z.object({}),
410
- execute: async () => ({ data: 'ok' }),
411
- }),
412
- )
413
- const executor = new ToolExecutor(registry)
414
-
415
- // Two LLM calls: first triggers a tool, second is the final response
416
- const resp1: LLMResponse = {
417
- id: 'r1',
418
- content: [{ type: 'tool_use', id: 'tu1', name: 'noop', input: {} }],
419
- model: 'mock-model',
420
- stop_reason: 'tool_use',
421
- usage: { input_tokens: 100, output_tokens: 50 },
422
- }
423
- const resp2: LLMResponse = {
424
- id: 'r2',
425
- content: [{ type: 'text', text: 'Final answer' }],
426
- model: 'mock-model',
427
- stop_reason: 'end_turn',
428
- usage: { input_tokens: 200, output_tokens: 100 },
429
- }
430
-
431
- const adapter = mockAdapter([resp1, resp2])
432
- const runner = new AgentRunner(adapter, registry, executor, {
433
- model: 'mock-model',
434
- agentName: 'token-agent',
435
- })
436
-
437
- await runner.run(
438
- [{ role: 'user', content: [{ type: 'text', text: 'go' }] }],
439
- { onTrace: (e) => traces.push(e), runId: 'run-tok', traceAgent: 'token-agent' },
440
- )
441
-
442
- const llmTraces = traces.filter(t => t.type === 'llm_call')
443
- expect(llmTraces).toHaveLength(2)
444
-
445
- // Each trace carries its own turn's token usage, not the aggregate
446
- expect(llmTraces[0]!.tokens).toEqual({ input_tokens: 100, output_tokens: 50 })
447
- expect(llmTraces[1]!.tokens).toEqual({ input_tokens: 200, output_tokens: 100 })
448
-
449
- // Turn numbers should be sequential
450
- expect(llmTraces[0]!.turn).toBe(1)
451
- expect(llmTraces[1]!.turn).toBe(2)
452
- })
453
- })
package/tsconfig.json DELETED
@@ -1,25 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "lib": ["ES2022"],
7
- "outDir": "dist",
8
- "rootDir": "src",
9
- "declaration": true,
10
- "declarationMap": true,
11
- "sourceMap": true,
12
- "strict": true,
13
- "esModuleInterop": true,
14
- "skipLibCheck": true,
15
- "forceConsistentCasingInFileNames": true,
16
- "resolveJsonModule": true,
17
- "isolatedModules": true,
18
- "noUnusedLocals": false,
19
- "noUnusedParameters": false,
20
- "noImplicitReturns": true,
21
- "noFallthroughCasesInSwitch": true
22
- },
23
- "include": ["src/**/*"],
24
- "exclude": ["node_modules", "dist", "examples", "tests"]
25
- }
package/vitest.config.ts DELETED
@@ -1,9 +0,0 @@
1
- import { defineConfig } from 'vitest/config'
2
-
3
- export default defineConfig({
4
- test: {
5
- coverage: {
6
- include: ['src/**'],
7
- },
8
- },
9
- })