@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.
- package/package.json +8 -2
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -23
- package/.github/pull_request_template.md +0 -14
- package/.github/workflows/ci.yml +0 -23
- package/CLAUDE.md +0 -80
- package/CODE_OF_CONDUCT.md +0 -48
- package/CONTRIBUTING.md +0 -72
- package/DECISIONS.md +0 -43
- package/README_zh.md +0 -277
- package/SECURITY.md +0 -17
- package/examples/01-single-agent.ts +0 -131
- package/examples/02-team-collaboration.ts +0 -167
- package/examples/03-task-pipeline.ts +0 -201
- package/examples/04-multi-model-team.ts +0 -261
- package/examples/05-copilot-test.ts +0 -49
- package/examples/06-local-model.ts +0 -200
- package/examples/07-fan-out-aggregate.ts +0 -209
- package/examples/08-gemma4-local.ts +0 -192
- package/examples/09-structured-output.ts +0 -73
- package/examples/10-task-retry.ts +0 -132
- package/examples/11-trace-observability.ts +0 -133
- package/examples/12-grok.ts +0 -154
- package/examples/13-gemini.ts +0 -48
- package/src/agent/agent.ts +0 -622
- package/src/agent/loop-detector.ts +0 -137
- package/src/agent/pool.ts +0 -285
- package/src/agent/runner.ts +0 -542
- package/src/agent/structured-output.ts +0 -126
- package/src/index.ts +0 -182
- package/src/llm/adapter.ts +0 -98
- package/src/llm/anthropic.ts +0 -389
- package/src/llm/copilot.ts +0 -552
- package/src/llm/gemini.ts +0 -378
- package/src/llm/grok.ts +0 -29
- package/src/llm/openai-common.ts +0 -294
- package/src/llm/openai.ts +0 -292
- package/src/memory/shared.ts +0 -181
- package/src/memory/store.ts +0 -124
- package/src/orchestrator/orchestrator.ts +0 -1071
- package/src/orchestrator/scheduler.ts +0 -352
- package/src/task/queue.ts +0 -464
- package/src/task/task.ts +0 -239
- package/src/team/messaging.ts +0 -232
- package/src/team/team.ts +0 -334
- package/src/tool/built-in/bash.ts +0 -187
- package/src/tool/built-in/file-edit.ts +0 -154
- package/src/tool/built-in/file-read.ts +0 -105
- package/src/tool/built-in/file-write.ts +0 -81
- package/src/tool/built-in/grep.ts +0 -362
- package/src/tool/built-in/index.ts +0 -50
- package/src/tool/executor.ts +0 -178
- package/src/tool/framework.ts +0 -557
- package/src/tool/text-tool-extractor.ts +0 -219
- package/src/types.ts +0 -542
- package/src/utils/semaphore.ts +0 -89
- package/src/utils/trace.ts +0 -34
- package/tests/agent-hooks.test.ts +0 -473
- package/tests/agent-pool.test.ts +0 -212
- package/tests/approval.test.ts +0 -464
- package/tests/built-in-tools.test.ts +0 -393
- package/tests/gemini-adapter.test.ts +0 -97
- package/tests/grok-adapter.test.ts +0 -74
- package/tests/llm-adapters.test.ts +0 -357
- package/tests/loop-detection.test.ts +0 -456
- package/tests/openai-fallback.test.ts +0 -159
- package/tests/orchestrator.test.ts +0 -281
- package/tests/scheduler.test.ts +0 -221
- package/tests/semaphore.test.ts +0 -57
- package/tests/shared-memory.test.ts +0 -122
- package/tests/structured-output.test.ts +0 -331
- package/tests/task-queue.test.ts +0 -244
- package/tests/task-retry.test.ts +0 -368
- package/tests/task-utils.test.ts +0 -155
- package/tests/team-messaging.test.ts +0 -329
- package/tests/text-tool-extractor.test.ts +0 -170
- package/tests/tool-executor.test.ts +0 -193
- package/tests/trace.test.ts +0 -453
- package/tsconfig.json +0 -25
- package/vitest.config.ts +0 -9
package/tests/trace.test.ts
DELETED
|
@@ -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
|
-
}
|