connectonion 0.4.12__py3-none-any.whl → 0.5.1__py3-none-any.whl

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 (74) hide show
  1. connectonion/__init__.py +11 -5
  2. connectonion/agent.py +44 -42
  3. connectonion/cli/commands/init.py +1 -1
  4. connectonion/cli/commands/project_cmd_lib.py +4 -4
  5. connectonion/cli/commands/reset_commands.py +1 -1
  6. connectonion/cli/docs/co-vibecoding-principles-docs-contexts-all-in-one.md +15 -11
  7. connectonion/cli/templates/minimal/agent.py +2 -2
  8. connectonion/console.py +55 -3
  9. connectonion/events.py +96 -17
  10. connectonion/llm.py +21 -3
  11. connectonion/logger.py +289 -0
  12. connectonion/prompt_files/eval_expected.md +12 -0
  13. connectonion/tool_executor.py +43 -32
  14. connectonion/usage.py +4 -0
  15. connectonion/useful_events_handlers/reflect.py +13 -9
  16. connectonion/useful_plugins/__init__.py +2 -1
  17. connectonion/useful_plugins/calendar_plugin.py +2 -2
  18. connectonion/useful_plugins/eval.py +130 -0
  19. connectonion/useful_plugins/gmail_plugin.py +4 -4
  20. connectonion/useful_plugins/image_result_formatter.py +4 -3
  21. connectonion/useful_plugins/re_act.py +14 -56
  22. connectonion/useful_plugins/shell_approval.py +2 -2
  23. connectonion/useful_tools/memory.py +4 -0
  24. {connectonion-0.4.12.dist-info → connectonion-0.5.1.dist-info}/METADATA +49 -49
  25. {connectonion-0.4.12.dist-info → connectonion-0.5.1.dist-info}/RECORD +27 -71
  26. {connectonion-0.4.12.dist-info → connectonion-0.5.1.dist-info}/WHEEL +1 -2
  27. connectonion/cli/templates/email-agent/.env.example +0 -23
  28. connectonion/cli/templates/email-agent/README.md +0 -240
  29. connectonion/cli/templates/email-agent/agent.py +0 -374
  30. connectonion/cli/templates/email-agent/demo.py +0 -71
  31. connectonion/cli/templates/meta-agent/.env.example +0 -11
  32. connectonion/cli/templates/minimal/.env.example +0 -5
  33. connectonion/cli/templates/playwright/.env.example +0 -5
  34. connectonion-0.4.12.dist-info/top_level.txt +0 -2
  35. tests/__init__.py +0 -0
  36. tests/cli/__init__.py +0 -1
  37. tests/cli/argparse_runner.py +0 -85
  38. tests/cli/conftest.py +0 -5
  39. tests/cli/test_browser_cli.py +0 -61
  40. tests/cli/test_cli.py +0 -143
  41. tests/cli/test_cli_auth_google.py +0 -344
  42. tests/cli/test_cli_auth_microsoft.py +0 -256
  43. tests/cli/test_cli_create.py +0 -283
  44. tests/cli/test_cli_help.py +0 -200
  45. tests/cli/test_cli_init.py +0 -318
  46. tests/conftest.py +0 -283
  47. tests/debug_gemini_models.py +0 -23
  48. tests/fixtures/__init__.py +0 -1
  49. tests/fixtures/test_tools.py +0 -112
  50. tests/fixtures/trust_fixtures.py +0 -257
  51. tests/real_api/__init__.py +0 -0
  52. tests/real_api/conftest.py +0 -9
  53. tests/real_api/test_llm_do.py +0 -174
  54. tests/real_api/test_llm_do_comprehensive.py +0 -527
  55. tests/real_api/test_production_client.py +0 -94
  56. tests/real_api/test_real_anthropic.py +0 -100
  57. tests/real_api/test_real_api.py +0 -113
  58. tests/real_api/test_real_auth.py +0 -130
  59. tests/real_api/test_real_email.py +0 -95
  60. tests/real_api/test_real_gemini.py +0 -96
  61. tests/real_api/test_real_llm_do.py +0 -81
  62. tests/real_api/test_real_managed.py +0 -208
  63. tests/real_api/test_real_multi_llm.py +0 -454
  64. tests/real_api/test_real_openai.py +0 -100
  65. tests/real_api/test_responses_parse.py +0 -88
  66. tests/test_diff_writer.py +0 -126
  67. tests/test_events.py +0 -677
  68. tests/test_gemini_co.py +0 -70
  69. tests/test_image_result_formatter.py +0 -88
  70. tests/test_plugin_system.py +0 -110
  71. tests/utils/__init__.py +0 -1
  72. tests/utils/config_helpers.py +0 -188
  73. tests/utils/mock_helpers.py +0 -237
  74. {connectonion-0.4.12.dist-info → connectonion-0.5.1.dist-info}/entry_points.txt +0 -0
tests/test_events.py DELETED
@@ -1,677 +0,0 @@
1
- """
2
- Tests for the event system (on_events parameter)
3
- """
4
- import pytest
5
- from unittest.mock import Mock
6
- from connectonion import Agent, after_user_input, before_llm, after_llm, before_tool, after_tool, on_error, on_complete
7
-
8
-
9
- def search(query: str) -> str:
10
- """Mock search tool"""
11
- return f"Results for {query}"
12
-
13
-
14
- def failing_tool(query: str) -> str:
15
- """Tool that always fails"""
16
- raise ValueError("Intentional failure")
17
-
18
-
19
- class TestEventSystem:
20
- """Test event system functionality"""
21
-
22
- def test_after_user_input_fires_once(self):
23
- """Test after_user_input fires once per turn"""
24
- calls = []
25
-
26
- def track_user_input(agent):
27
- calls.append('after_user_input')
28
- # Verify we can access user prompt
29
- assert agent.current_session['user_prompt'] == "test prompt"
30
-
31
- agent = Agent(
32
- "test",
33
- model="gpt-4o-mini",
34
- on_events=[after_user_input(track_user_input)]
35
- )
36
-
37
- agent.input("test prompt")
38
-
39
- # Should fire exactly once per turn
40
- assert calls == ['after_user_input']
41
-
42
- def test_before_llm_fires_multiple_times(self):
43
- """Test before_llm fires before each LLM call"""
44
- calls = []
45
-
46
- def track_before_llm(agent):
47
- calls.append('before_llm')
48
-
49
- agent = Agent(
50
- "test",
51
- tools=[search],
52
- model="gpt-4o-mini",
53
- on_events=[before_llm(track_before_llm)]
54
- )
55
-
56
- agent.input("Search for Python")
57
-
58
- # Should fire multiple times (once per LLM call in iteration loop)
59
- assert len(calls) >= 1
60
- assert all(c == 'before_llm' for c in calls)
61
-
62
- def test_after_llm_fires_multiple_times(self):
63
- """Test after_llm fires after each LLM response"""
64
- calls = []
65
-
66
- def track_after_llm(agent):
67
- calls.append('after_llm')
68
- # Verify we can access trace
69
- trace = agent.current_session['trace']
70
- llm_calls = [t for t in trace if t['type'] == 'llm_call']
71
- assert len(llm_calls) > 0
72
-
73
- agent = Agent(
74
- "test",
75
- tools=[search],
76
- model="gpt-4o-mini",
77
- on_events=[after_llm(track_after_llm)]
78
- )
79
-
80
- agent.input("Search for Python")
81
-
82
- # Should fire multiple times
83
- assert len(calls) >= 1
84
- assert all(c == 'after_llm' for c in calls)
85
-
86
- def test_before_tool_fires_before_execution(self):
87
- """Test before_tool fires before each tool execution"""
88
- calls = []
89
-
90
- def track_before_tool(agent):
91
- calls.append('before_tool')
92
- # Verify trace doesn't have result yet (tool hasn't run)
93
- trace = agent.current_session['trace']
94
- tool_executions = [t for t in trace if t['type'] == 'tool_execution']
95
- # Before tool runs, latest tool execution should be pending or not exist yet
96
- if tool_executions:
97
- # The trace entry exists but hasn't been updated with result yet
98
- pass
99
-
100
- agent = Agent(
101
- "test",
102
- tools=[search],
103
- model="gpt-4o-mini",
104
- on_events=[before_tool(track_before_tool)]
105
- )
106
-
107
- agent.input("Search for Python")
108
-
109
- # Should fire at least once (when search tool is called)
110
- assert len(calls) >= 1
111
-
112
- def test_after_tool_fires_after_success(self):
113
- """Test after_tool fires after successful tool execution"""
114
- calls = []
115
-
116
- def track_after_tool(agent):
117
- calls.append('after_tool')
118
- # Verify we can access tool result
119
- trace = agent.current_session['trace']
120
- tool_executions = [t for t in trace if t['type'] == 'tool_execution']
121
- assert len(tool_executions) > 0
122
- latest_tool = tool_executions[-1]
123
- assert latest_tool['status'] == 'success'
124
- assert 'Results for' in latest_tool['result']
125
-
126
- agent = Agent(
127
- "test",
128
- tools=[search],
129
- model="gpt-4o-mini",
130
- on_events=[after_tool(track_after_tool)]
131
- )
132
-
133
- agent.input("Search for Python")
134
-
135
- # Should fire at least once
136
- assert len(calls) >= 1
137
-
138
- def test_on_error_fires_on_tool_failure(self):
139
- """Test on_error fires when tool execution fails"""
140
- calls = []
141
-
142
- def track_error(agent):
143
- calls.append('on_error')
144
- # Verify we can access error information
145
- trace = agent.current_session['trace']
146
- tool_executions = [t for t in trace if t['type'] == 'tool_execution']
147
- assert len(tool_executions) > 0
148
- latest_tool = tool_executions[-1]
149
- assert latest_tool['status'] == 'error'
150
- assert 'error' in latest_tool
151
- assert latest_tool['error'] == 'Intentional failure'
152
-
153
- agent = Agent(
154
- "test",
155
- tools=[failing_tool],
156
- model="gpt-4o-mini",
157
- on_events=[on_error(track_error)]
158
- )
159
-
160
- # This should trigger the failing tool
161
- result = agent.input("Use failing_tool with query 'test'")
162
-
163
- # Should fire at least once
164
- assert len(calls) >= 1
165
-
166
- def test_multiple_events_same_type_fire_in_order(self):
167
- """Test multiple events of same type fire in order"""
168
- calls = []
169
-
170
- def handler1(agent):
171
- calls.append('handler1')
172
-
173
- def handler2(agent):
174
- calls.append('handler2')
175
-
176
- def handler3(agent):
177
- calls.append('handler3')
178
-
179
- agent = Agent(
180
- "test",
181
- model="gpt-4o-mini",
182
- on_events=[
183
- after_user_input(handler1),
184
- after_user_input(handler2),
185
- after_user_input(handler3)
186
- ]
187
- )
188
-
189
- agent.input("test")
190
-
191
- # Should fire in order
192
- assert calls == ['handler1', 'handler2', 'handler3']
193
-
194
- def test_events_can_modify_messages(self):
195
- """Test events can modify agent messages"""
196
-
197
- def add_system_message(agent):
198
- agent.current_session['messages'].append({
199
- 'role': 'system',
200
- 'content': 'Added by event'
201
- })
202
-
203
- agent = Agent(
204
- "test",
205
- model="gpt-4o-mini",
206
- on_events=[after_user_input(add_system_message)]
207
- )
208
-
209
- agent.input("test")
210
-
211
- # Verify message was added
212
- messages = agent.current_session['messages']
213
- system_messages = [m for m in messages if m['role'] == 'system' and m['content'] == 'Added by event']
214
- assert len(system_messages) == 1
215
-
216
- def test_event_exception_propagates(self):
217
- """Test that event exceptions propagate (fail fast)"""
218
-
219
- def failing_event(agent):
220
- raise RuntimeError("Event failed")
221
-
222
- agent = Agent(
223
- "test",
224
- model="gpt-4o-mini",
225
- on_events=[after_user_input(failing_event)]
226
- )
227
-
228
- # Event exception should propagate
229
- with pytest.raises(RuntimeError, match="Event failed"):
230
- agent.input("test")
231
-
232
- def test_mixed_event_types(self):
233
- """Test using multiple different event types together"""
234
- calls = []
235
-
236
- def track_user(agent):
237
- calls.append('after_user_input')
238
-
239
- def track_llm(agent):
240
- calls.append('after_llm')
241
-
242
- def track_tool(agent):
243
- calls.append('after_tool')
244
-
245
- agent = Agent(
246
- "test",
247
- tools=[search],
248
- model="gpt-4o-mini",
249
- on_events=[
250
- after_user_input(track_user),
251
- after_llm(track_llm),
252
- after_tool(track_tool)
253
- ]
254
- )
255
-
256
- agent.input("Search for Python")
257
-
258
- # All event types should fire
259
- assert 'after_user_input' in calls
260
- assert 'after_llm' in calls
261
- assert 'after_tool' in calls
262
-
263
- # after_user_input should fire exactly once
264
- assert calls.count('after_user_input') == 1
265
-
266
- def test_no_events_works_normally(self):
267
- """Test agent works normally without any events"""
268
- agent = Agent(
269
- "test",
270
- tools=[search],
271
- model="gpt-4o-mini"
272
- )
273
-
274
- result = agent.input("Search for Python")
275
-
276
- # Should complete normally
277
- assert result is not None
278
-
279
- def test_event_receives_agent_instance(self):
280
- """Test that events receive the agent instance with all attributes"""
281
-
282
- def verify_agent_access(agent):
283
- # Should have access to all agent attributes
284
- assert hasattr(agent, 'name')
285
- assert hasattr(agent, 'current_session')
286
- assert hasattr(agent, 'tools')
287
- assert hasattr(agent, 'llm')
288
- assert hasattr(agent, 'console')
289
- assert agent.name == "test"
290
-
291
- agent = Agent(
292
- "test",
293
- model="gpt-4o-mini",
294
- on_events=[after_user_input(verify_agent_access)]
295
- )
296
-
297
- agent.input("test")
298
-
299
- def test_event_wrapper_adds_attribute(self):
300
- """Test that event wrappers add _event_type attribute"""
301
-
302
- def my_handler(agent):
303
- pass
304
-
305
- wrapped = after_llm(my_handler)
306
-
307
- assert hasattr(wrapped, '_event_type')
308
- assert wrapped._event_type == 'after_llm'
309
-
310
- def test_all_event_types_have_correct_attributes(self):
311
- """Test all event wrapper types set correct _event_type"""
312
-
313
- def handler(agent):
314
- pass
315
-
316
- assert after_user_input(handler)._event_type == 'after_user_input'
317
- assert before_llm(handler)._event_type == 'before_llm'
318
- assert after_llm(handler)._event_type == 'after_llm'
319
- assert before_tool(handler)._event_type == 'before_tool'
320
- assert after_tool(handler)._event_type == 'after_tool'
321
- assert on_error(handler)._event_type == 'on_error'
322
- assert on_complete(handler)._event_type == 'on_complete'
323
-
324
- def test_event_validation_rejects_non_callable(self):
325
- """Test that non-callable events are rejected with clear error"""
326
-
327
- with pytest.raises(TypeError, match="Event must be callable"):
328
- Agent(
329
- "test",
330
- model="gpt-4o-mini",
331
- on_events=["not a function"] # String instead of callable
332
- )
333
-
334
- def test_event_validation_rejects_missing_event_type(self):
335
- """Test that events without _event_type are rejected with helpful error"""
336
-
337
- def my_handler(agent):
338
- pass
339
-
340
- with pytest.raises(ValueError, match="missing _event_type.*Did you forget to wrap it"):
341
- Agent(
342
- "test",
343
- model="gpt-4o-mini",
344
- on_events=[my_handler] # Not wrapped with after_llm(), etc.
345
- )
346
-
347
- def test_event_validation_rejects_invalid_event_type(self):
348
- """Test that events with invalid _event_type are rejected"""
349
-
350
- def my_handler(agent):
351
- pass
352
-
353
- my_handler._event_type = 'invalid_event_type'
354
-
355
- with pytest.raises(ValueError, match="Invalid event type"):
356
- Agent(
357
- "test",
358
- model="gpt-4o-mini",
359
- on_events=[my_handler]
360
- )
361
-
362
- def test_on_error_fires_on_tool_not_found(self):
363
- """Test on_error fires when tool is not found (consistency with tool execution errors)"""
364
- calls = []
365
-
366
- def track_error(agent):
367
- calls.append('on_error')
368
- # Verify we can access error information
369
- trace = agent.current_session['trace']
370
- tool_executions = [t for t in trace if t['type'] == 'tool_execution']
371
- assert len(tool_executions) > 0
372
- latest_tool = tool_executions[-1]
373
- assert latest_tool['status'] == 'not_found'
374
- assert 'error' in latest_tool
375
- assert 'not found' in latest_tool['error']
376
-
377
- agent = Agent(
378
- "test",
379
- tools=[search], # Only has 'search' tool
380
- model="gpt-4o-mini",
381
- on_events=[on_error(track_error)]
382
- )
383
-
384
- # Directly execute a nonexistent tool to trigger tool not found
385
- result = agent.execute_tool("nonexistent_tool", {"query": "test"})
386
-
387
- # Should fire on_error exactly once
388
- assert len(calls) == 1
389
-
390
- def test_on_complete_fires_once_per_input(self):
391
- """Test on_complete fires exactly once per input() call, after final response"""
392
- calls = []
393
-
394
- def track_complete(agent):
395
- calls.append('on_complete')
396
- # Verify session exists and has response
397
- assert agent.current_session is not None
398
- assert 'trace' in agent.current_session
399
-
400
- agent = Agent(
401
- "test",
402
- tools=[search],
403
- model="gpt-4o-mini",
404
- on_events=[on_complete(track_complete)]
405
- )
406
-
407
- # First input
408
- agent.input("Search for Python")
409
- assert len(calls) == 1
410
-
411
- # Second input
412
- agent.input("Search for JavaScript")
413
- assert len(calls) == 2
414
-
415
- def test_on_complete_fires_after_tool_execution(self):
416
- """Test on_complete fires after all tools have completed"""
417
- event_order = []
418
-
419
- def track_after_tool(agent):
420
- event_order.append('after_tool')
421
-
422
- def track_complete(agent):
423
- event_order.append('on_complete')
424
-
425
- agent = Agent(
426
- "test",
427
- tools=[search],
428
- model="gpt-4o-mini",
429
- on_events=[
430
- after_tool(track_after_tool),
431
- on_complete(track_complete)
432
- ]
433
- )
434
-
435
- agent.input("Search for Python")
436
-
437
- # on_complete should be the last event
438
- assert event_order[-1] == 'on_complete'
439
- # after_tool should fire before on_complete
440
- assert 'after_tool' in event_order
441
- assert event_order.index('after_tool') < event_order.index('on_complete')
442
-
443
- def test_after_tool_fires_for_all_executions(self):
444
- """Test after_tool fires for ALL tool executions (success, error, not_found)"""
445
- after_tool_calls = []
446
- on_error_calls = []
447
-
448
- def track_after_tool(agent):
449
- after_tool_calls.append(agent.current_session['trace'][-1]['status'])
450
-
451
- def track_error(agent):
452
- on_error_calls.append(agent.current_session['trace'][-1]['status'])
453
-
454
- agent = Agent(
455
- "test",
456
- tools=[search, failing_tool],
457
- model="gpt-4o-mini",
458
- on_events=[after_tool(track_after_tool), on_error(track_error)]
459
- )
460
-
461
- # Success case
462
- agent.execute_tool("search", {"query": "test"})
463
-
464
- # Error case
465
- agent.execute_tool("failing_tool", {"query": "test"})
466
-
467
- # Not found case
468
- agent.execute_tool("nonexistent", {})
469
-
470
- # after_tool should fire for all 3 executions
471
- assert len(after_tool_calls) == 3
472
- assert after_tool_calls == ['success', 'error', 'not_found']
473
-
474
- # on_error should fire only for error and not_found (2 times)
475
- assert len(on_error_calls) == 2
476
- assert on_error_calls == ['error', 'not_found']
477
-
478
-
479
- class TestNewEventSyntax:
480
- """Test new decorator and multiple-args wrapper syntax"""
481
-
482
- def test_decorator_syntax_single_function(self):
483
- """Test @before_tool decorator returns single callable function"""
484
-
485
- @before_tool
486
- def check_something(agent):
487
- pass
488
-
489
- # Should be callable (not a list)
490
- assert callable(check_something)
491
- assert hasattr(check_something, '_event_type')
492
- assert check_something._event_type == 'before_tool'
493
-
494
- def test_decorator_syntax_all_event_types(self):
495
- """Test decorator syntax works for all event types"""
496
-
497
- @after_user_input
498
- def handler1(agent):
499
- pass
500
-
501
- @before_llm
502
- def handler2(agent):
503
- pass
504
-
505
- @after_llm
506
- def handler3(agent):
507
- pass
508
-
509
- @before_tool
510
- def handler4(agent):
511
- pass
512
-
513
- @after_tool
514
- def handler5(agent):
515
- pass
516
-
517
- @on_error
518
- def handler6(agent):
519
- pass
520
-
521
- @on_complete
522
- def handler7(agent):
523
- pass
524
-
525
- # All should be callable
526
- assert callable(handler1)
527
- assert callable(handler2)
528
- assert callable(handler3)
529
- assert callable(handler4)
530
- assert callable(handler5)
531
- assert callable(handler6)
532
- assert callable(handler7)
533
-
534
- # All should have correct _event_type
535
- assert handler1._event_type == 'after_user_input'
536
- assert handler2._event_type == 'before_llm'
537
- assert handler3._event_type == 'after_llm'
538
- assert handler4._event_type == 'before_tool'
539
- assert handler5._event_type == 'after_tool'
540
- assert handler6._event_type == 'on_error'
541
- assert handler7._event_type == 'on_complete'
542
-
543
- def test_wrapper_multiple_args_returns_list(self):
544
- """Test before_tool(fn1, fn2) returns list"""
545
-
546
- def handler1(agent):
547
- pass
548
-
549
- def handler2(agent):
550
- pass
551
-
552
- result = before_tool(handler1, handler2)
553
-
554
- # Should be a list
555
- assert isinstance(result, list)
556
- assert len(result) == 2
557
-
558
- # Both should have _event_type
559
- assert result[0]._event_type == 'before_tool'
560
- assert result[1]._event_type == 'before_tool'
561
-
562
- def test_wrapper_single_arg_returns_function(self):
563
- """Test before_tool(fn) returns single function (backward compatible)"""
564
-
565
- def handler(agent):
566
- pass
567
-
568
- result = before_tool(handler)
569
-
570
- # Should be callable, not list
571
- assert callable(result)
572
- assert not isinstance(result, list)
573
- assert result._event_type == 'before_tool'
574
-
575
- def test_agent_accepts_decorated_handlers(self):
576
- """Test Agent accepts @decorated handlers in on_events"""
577
-
578
- @before_tool
579
- def check_tool(agent):
580
- pass
581
-
582
- @after_tool
583
- def log_tool(agent):
584
- pass
585
-
586
- agent = Agent(
587
- "test",
588
- model="gpt-4o-mini",
589
- on_events=[check_tool, log_tool],
590
- log=False
591
- )
592
-
593
- assert len(agent.events['before_tool']) == 1
594
- assert len(agent.events['after_tool']) == 1
595
-
596
- def test_agent_accepts_multiple_args_wrapper(self):
597
- """Test Agent accepts before_tool(fn1, fn2) in on_events"""
598
-
599
- def handler1(agent):
600
- pass
601
-
602
- def handler2(agent):
603
- pass
604
-
605
- def handler3(agent):
606
- pass
607
-
608
- agent = Agent(
609
- "test",
610
- model="gpt-4o-mini",
611
- on_events=[
612
- before_tool(handler1, handler2), # returns [fn, fn]
613
- after_tool(handler3), # returns fn
614
- ],
615
- log=False
616
- )
617
-
618
- # Should have 2 before_tool handlers and 1 after_tool handler
619
- assert len(agent.events['before_tool']) == 2
620
- assert len(agent.events['after_tool']) == 1
621
-
622
- def test_mixed_decorator_and_wrapper_syntax(self):
623
- """Test mixing @decorator and wrapper(fn1, fn2) syntax"""
624
-
625
- @before_tool
626
- def decorated_handler(agent):
627
- pass
628
-
629
- def wrapper_handler1(agent):
630
- pass
631
-
632
- def wrapper_handler2(agent):
633
- pass
634
-
635
- agent = Agent(
636
- "test",
637
- model="gpt-4o-mini",
638
- on_events=[
639
- decorated_handler, # @decorator
640
- before_tool(wrapper_handler1, wrapper_handler2), # wrapper(fn1, fn2)
641
- ],
642
- log=False
643
- )
644
-
645
- # Should have 3 before_tool handlers total
646
- assert len(agent.events['before_tool']) == 3
647
-
648
- def test_handlers_fire_in_order(self):
649
- """Test handlers fire in correct order regardless of syntax"""
650
- calls = []
651
-
652
- @after_user_input
653
- def handler1(agent):
654
- calls.append('handler1')
655
-
656
- def handler2(agent):
657
- calls.append('handler2')
658
-
659
- def handler3(agent):
660
- calls.append('handler3')
661
-
662
- agent = Agent(
663
- "test",
664
- model="gpt-4o-mini",
665
- on_events=[
666
- handler1, # decorated
667
- after_user_input(handler2, handler3), # wrapper with multiple
668
- ],
669
- log=False
670
- )
671
-
672
- # Manually invoke to test order
673
- agent.current_session = {'user_prompt': 'test'}
674
- agent._invoke_events('after_user_input')
675
-
676
- # Should fire in registration order
677
- assert calls == ['handler1', 'handler2', 'handler3']