auto-coder 0.1.301__py3-none-any.whl → 0.1.303__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.

Potentially problematic release.


This version of auto-coder might be problematic. Click here for more details.

Files changed (33) hide show
  1. {auto_coder-0.1.301.dist-info → auto_coder-0.1.303.dist-info}/METADATA +2 -1
  2. {auto_coder-0.1.301.dist-info → auto_coder-0.1.303.dist-info}/RECORD +33 -25
  3. autocoder/auto_coder.py +6 -7
  4. autocoder/auto_coder_runner.py +9 -9
  5. autocoder/chat_auto_coder.py +6 -1
  6. autocoder/commands/auto_command.py +313 -205
  7. autocoder/commands/tools.py +123 -85
  8. autocoder/common/__init__.py +2 -0
  9. autocoder/common/action_yml_file_manager.py +28 -6
  10. autocoder/common/auto_coder_lang.py +2 -2
  11. autocoder/common/auto_configure.py +9 -4
  12. autocoder/common/code_auto_merge.py +1 -1
  13. autocoder/common/code_auto_merge_diff.py +1 -1
  14. autocoder/common/code_auto_merge_editblock.py +1 -1
  15. autocoder/common/code_auto_merge_strict_diff.py +1 -1
  16. autocoder/common/stream_out_type.py +7 -0
  17. autocoder/dispacher/actions/action.py +221 -101
  18. autocoder/dispacher/actions/plugins/action_regex_project.py +18 -0
  19. autocoder/events/__init__.py +57 -0
  20. autocoder/events/event_content.py +423 -0
  21. autocoder/events/event_manager.py +327 -0
  22. autocoder/events/event_manager_singleton.py +245 -0
  23. autocoder/events/event_store.py +376 -0
  24. autocoder/events/event_types.py +103 -0
  25. autocoder/index/entry.py +88 -60
  26. autocoder/index/filter/quick_filter.py +71 -3
  27. autocoder/run_context.py +62 -0
  28. autocoder/utils/auto_coder_utils/chat_stream_out.py +32 -1
  29. autocoder/version.py +1 -1
  30. {auto_coder-0.1.301.dist-info → auto_coder-0.1.303.dist-info}/LICENSE +0 -0
  31. {auto_coder-0.1.301.dist-info → auto_coder-0.1.303.dist-info}/WHEEL +0 -0
  32. {auto_coder-0.1.301.dist-info → auto_coder-0.1.303.dist-info}/entry_points.txt +0 -0
  33. {auto_coder-0.1.301.dist-info → auto_coder-0.1.303.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,376 @@
1
+ """
2
+ Event store implementation for storing and retrieving events.
3
+ """
4
+
5
+ import os
6
+ import json
7
+ import time
8
+ import threading
9
+ from typing import List, Optional, Dict, Any, Iterator, Union, Callable, Tuple
10
+ from abc import ABC, abstractmethod
11
+ # import fcntl
12
+ from pathlib import Path
13
+ from readerwriterlock import rwlock
14
+
15
+ from .event_types import Event, EventType, ResponseEvent
16
+
17
+
18
+ class EventStore(ABC):
19
+ """
20
+ Abstract base class for event stores.
21
+ """
22
+
23
+ @abstractmethod
24
+ def append_event(self, event: Event) -> None:
25
+ """
26
+ Append an event to the store.
27
+
28
+ Args:
29
+ event: The event to append
30
+ """
31
+ pass
32
+
33
+ @abstractmethod
34
+ def get_events(self,
35
+ after_id: Optional[str] = None,
36
+ event_types: Optional[List[EventType]] = None,
37
+ limit: Optional[int] = None) -> List[Event]:
38
+ """
39
+ Get events from the store with optional filtering.
40
+
41
+ Args:
42
+ after_id: Only get events after this ID
43
+ event_types: Only get events of these types
44
+ limit: Maximum number of events to return
45
+
46
+ Returns:
47
+ List of events matching the criteria
48
+ """
49
+ pass
50
+
51
+ @abstractmethod
52
+ def get_event_by_id(self, event_id: str) -> Optional[Event]:
53
+ """
54
+ Get a specific event by ID.
55
+
56
+ Args:
57
+ event_id: The ID of the event to retrieve
58
+
59
+ Returns:
60
+ The event if found, None otherwise
61
+ """
62
+ pass
63
+
64
+ @abstractmethod
65
+ def wait_for_event(self,
66
+ condition: Callable[[Event], bool],
67
+ timeout: Optional[float] = None) -> Optional[Event]:
68
+ """
69
+ Wait for an event matching the given condition.
70
+
71
+ Args:
72
+ condition: Function that takes an event and returns True if it matches
73
+ timeout: Maximum time to wait in seconds, or None to wait indefinitely
74
+
75
+ Returns:
76
+ The matching event, or None if timeout occurs
77
+ """
78
+ pass
79
+
80
+ @abstractmethod
81
+ def truncate(self) -> None:
82
+ """
83
+ Clear the event store by truncating the file.
84
+ This removes all events from the store.
85
+ """
86
+ pass
87
+
88
+
89
+ class JsonlEventStore(EventStore):
90
+ """
91
+ Event store implementation using JSONL files.
92
+ This implementation uses reader-writer locks and file locks to ensure thread safety.
93
+ """
94
+
95
+ def __init__(self, file_path: str, flush_interval: float = 0.1):
96
+ """
97
+ Initialize a JSONL event store.
98
+
99
+ Args:
100
+ file_path: Path to the JSONL file
101
+ flush_interval: How often to flush events to disk (in seconds)
102
+ """
103
+ self.file_path = file_path
104
+ self.flush_interval = flush_interval
105
+ self.rwlock = rwlock.RWLockFair()
106
+ self._last_event_id: Optional[str] = None
107
+ self._watchers: List[threading.Event] = []
108
+
109
+ # Create directory if it doesn't exist
110
+ os.makedirs(os.path.dirname(os.path.abspath(file_path)), exist_ok=True)
111
+
112
+ # Create file if it doesn't exist
113
+ if not os.path.exists(file_path):
114
+ with open(file_path, 'w', encoding='utf-8') as f:
115
+ pass
116
+
117
+ # Update last event ID from file
118
+ self._update_last_event_id()
119
+
120
+ # Start a background thread to monitor for new events
121
+ self._stop_monitoring = threading.Event()
122
+ self._monitor_thread = threading.Thread(target=self._monitor_events, daemon=True)
123
+ self._monitor_thread.start()
124
+
125
+ def _update_last_event_id(self) -> None:
126
+ """Update the last event ID by reading the last event from the file."""
127
+ with self.rwlock.gen_rlock():
128
+ try:
129
+ with open(self.file_path, 'r', encoding='utf-8') as f:
130
+ # fcntl.flock(f, fcntl.LOCK_SH)
131
+ try:
132
+ # Get the last line of the file that contains a valid event
133
+ last_event = None
134
+ for line in f:
135
+ line = line.strip()
136
+ if not line:
137
+ continue
138
+ try:
139
+ event_data = json.loads(line)
140
+ if "event_type" not in event_data:
141
+ continue
142
+
143
+ if "response_to" in event_data:
144
+ event = ResponseEvent.from_dict(event_data)
145
+ else:
146
+ event = Event.from_dict(event_data)
147
+
148
+ last_event = event
149
+ except json.JSONDecodeError:
150
+ # Skip invalid lines
151
+ pass
152
+
153
+ if last_event:
154
+ self._last_event_id = last_event.event_id
155
+ finally:
156
+ # fcntl.flock(f, fcntl.LOCK_UN)
157
+ pass
158
+ except FileNotFoundError:
159
+ # File doesn't exist yet
160
+ pass
161
+
162
+ def _read_events_from_file(self) -> List[Event]:
163
+ """
164
+ Read all events from the file.
165
+
166
+ Returns:
167
+ List of all events from the file
168
+ """
169
+ events = []
170
+ with open(self.file_path, 'r', encoding='utf-8') as f:
171
+ for line in f:
172
+ line = line.strip()
173
+ if not line:
174
+ continue
175
+ try:
176
+ event_data = json.loads(line)
177
+ if "event_type" not in event_data:
178
+ continue
179
+
180
+ if "response_to" in event_data:
181
+ event = ResponseEvent.from_dict(event_data)
182
+ else:
183
+ event = Event.from_dict(event_data)
184
+
185
+ events.append(event)
186
+ except json.JSONDecodeError:
187
+ # Skip invalid lines
188
+ pass
189
+ return events
190
+
191
+ def append_event(self, event: Event) -> None:
192
+ """
193
+ Append an event to the store.
194
+
195
+ Args:
196
+ event: The event to append
197
+ """
198
+ with self.rwlock.gen_wlock():
199
+ with open(self.file_path, 'a', encoding='utf-8') as f:
200
+ # fcntl.flock(f, fcntl.LOCK_EX)
201
+ try:
202
+ f.write(event.to_json() + '\n')
203
+ f.flush()
204
+ finally:
205
+ # fcntl.flock(f, fcntl.LOCK_UN)
206
+ pass
207
+
208
+ self._last_event_id = event.event_id
209
+
210
+ # Notify all watchers
211
+ for watcher in self._watchers:
212
+ watcher.set()
213
+
214
+ def get_events(self,
215
+ after_id: Optional[str] = None,
216
+ event_types: Optional[List[EventType]] = None,
217
+ limit: Optional[int] = None) -> List[Event]:
218
+ """
219
+ Get events from the store with optional filtering.
220
+
221
+ Args:
222
+ after_id: Only get events after this ID
223
+ event_types: Only get events of these types
224
+ limit: Maximum number of events to return
225
+
226
+ Returns:
227
+ List of events matching the criteria
228
+ """
229
+ with self.rwlock.gen_rlock():
230
+ events = self._read_events_from_file()
231
+
232
+ # Sort by timestamp
233
+ events.sort(key=lambda e: e.timestamp)
234
+
235
+ # Filter by ID
236
+ if after_id:
237
+ after_index = None
238
+ for i, event in enumerate(events):
239
+ if event.event_id == after_id:
240
+ after_index = i
241
+ break
242
+
243
+ if after_index is not None:
244
+ events = events[after_index + 1:]
245
+ else:
246
+ events = []
247
+
248
+ # Filter by event type
249
+ if event_types:
250
+ events = [e for e in events if e.event_type in event_types]
251
+
252
+ # Apply limit
253
+ if limit is not None and limit > 0:
254
+ events = events[:limit]
255
+
256
+ return events
257
+
258
+ def get_event_by_id(self, event_id: str) -> Optional[Event]:
259
+ """
260
+ Get a specific event by ID.
261
+
262
+ Args:
263
+ event_id: The ID of the event to retrieve
264
+
265
+ Returns:
266
+ The event if found, None otherwise
267
+ """
268
+ with self.rwlock.gen_rlock():
269
+ events = self._read_events_from_file()
270
+
271
+ for event in events:
272
+ if event.event_id == event_id:
273
+ return event
274
+
275
+ return None
276
+
277
+ def wait_for_event(self,
278
+ condition: Callable[[Event], bool],
279
+ timeout: Optional[float] = None) -> Optional[Event]:
280
+ """
281
+ Wait for an event matching the given condition.
282
+
283
+ Args:
284
+ condition: Function that takes an event and returns True if it matches
285
+ timeout: Maximum time to wait in seconds, or None to wait indefinitely
286
+
287
+ Returns:
288
+ The matching event, or None if timeout occurs
289
+ """
290
+ watcher = threading.Event()
291
+
292
+ # Check if we already have a matching event
293
+ with self.rwlock.gen_rlock():
294
+ events = self._read_events_from_file()
295
+
296
+ for event in events:
297
+ if condition(event):
298
+ return event
299
+
300
+ # No existing event, add the watcher
301
+ self._watchers.append(watcher)
302
+
303
+ try:
304
+ # Wait for a notification
305
+ if watcher.wait(timeout):
306
+ # Check for new matching events
307
+ with self.rwlock.gen_rlock():
308
+ events = self._read_events_from_file()
309
+
310
+ for event in events:
311
+ if condition(event):
312
+ return event
313
+
314
+ # Timeout or no matching event
315
+ return None
316
+ finally:
317
+ # Remove the watcher
318
+ with self.rwlock.gen_wlock():
319
+ if watcher in self._watchers:
320
+ self._watchers.remove(watcher)
321
+
322
+ def _monitor_events(self) -> None:
323
+ """
324
+ Background thread to monitor for new events.
325
+ """
326
+ last_size = os.path.getsize(self.file_path)
327
+ last_check = time.time()
328
+
329
+ while not self._stop_monitoring.is_set():
330
+ try:
331
+ # Only check every flush_interval seconds
332
+ time.sleep(self.flush_interval)
333
+
334
+ current_time = time.time()
335
+ if current_time - last_check < self.flush_interval:
336
+ continue
337
+
338
+ last_check = current_time
339
+
340
+ # Check if file has changed
341
+ current_size = os.path.getsize(self.file_path)
342
+ if current_size > last_size:
343
+ self._update_last_event_id()
344
+ last_size = current_size
345
+
346
+ # Notify all watchers
347
+ for watcher in self._watchers:
348
+ watcher.set()
349
+ except Exception:
350
+ # Ignore any errors during monitoring
351
+ pass
352
+
353
+ def close(self) -> None:
354
+ """
355
+ Close the event store and stop monitoring.
356
+ """
357
+ self._stop_monitoring.set()
358
+ if self._monitor_thread.is_alive():
359
+ self._monitor_thread.join(timeout=1.0)
360
+
361
+ def truncate(self) -> None:
362
+ """
363
+ Clear the event store by truncating the file.
364
+ This removes all events from the store.
365
+ """
366
+ with self.rwlock.gen_wlock():
367
+ # Open the file in write mode with truncation ('w')
368
+ with open(self.file_path, 'w', encoding='utf-8') as f:
369
+ pass # Just open and close to truncate
370
+
371
+ # Reset the last event ID
372
+ self._last_event_id = None
373
+
374
+ # Notify all watchers
375
+ for watcher in self._watchers:
376
+ watcher.set()
@@ -0,0 +1,103 @@
1
+ """
2
+ Event type definitions for the event system.
3
+ """
4
+
5
+ from enum import Enum, auto
6
+ from typing import Dict, Any, Optional, List, Union
7
+ from dataclasses import dataclass, field
8
+ import uuid
9
+ import time
10
+ import json
11
+ from pydantic import BaseModel
12
+
13
+ class EventType(Enum):
14
+ """Event types supported by the system"""
15
+ RESULT = auto() # 结果数据
16
+ STREAM = auto() # 流式数据
17
+ ASK_USER = auto() # 请求用户输入
18
+ USER_RESPONSE = auto() # 用户响应
19
+ ERROR = auto() # 错误事件
20
+ COMPLETION = auto() # 完成事件
21
+
22
+
23
+ @dataclass
24
+ class Event:
25
+ """
26
+ Base event class for all events in the system.
27
+ """
28
+ event_type: EventType
29
+ timestamp: float = field(default_factory=time.time)
30
+ event_id: str = field(default_factory=lambda: str(uuid.uuid4()))
31
+ content: Dict[str, Any] = field(default_factory=dict)
32
+ metadata: Dict[str, Any] = field(default_factory=dict)
33
+
34
+ def to_dict(self) -> Dict[str, Any]:
35
+ """Convert event to dictionary for serialization"""
36
+ return {
37
+ "event_id": self.event_id,
38
+ "event_type": self.event_type.name,
39
+ "timestamp": self.timestamp,
40
+ "content": self.content,
41
+ "metadata": self.metadata
42
+ }
43
+
44
+ def to_json(self) -> str:
45
+ """Convert event to JSON string"""
46
+ return json.dumps(self.to_dict(),ensure_ascii=False)
47
+
48
+ @classmethod
49
+ def from_dict(cls, data: Dict[str, Any]) -> "Event":
50
+ """Create event from dictionary"""
51
+ event_type = EventType[data["event_type"]]
52
+ return cls(
53
+ event_type=event_type,
54
+ event_id=data.get("event_id", str(uuid.uuid4())),
55
+ timestamp=data.get("timestamp", time.time()),
56
+ content=data.get("content", {}),
57
+ metadata=data.get("metadata", {})
58
+ )
59
+
60
+ @classmethod
61
+ def from_json(cls, json_str: str) -> "Event":
62
+ """Create event from JSON string"""
63
+ return cls.from_dict(json.loads(json_str))
64
+
65
+
66
+ class EventMetadata(BaseModel):
67
+ action_file: str = field(default="") # 关联 auto-coder 中的action文件
68
+ stream_out_type: str = field(default="") # 标记,比如当前这个流式是用于什么场景的,比如用于产生命令的还是啥
69
+ is_streaming: bool = field(default=False) # 是否是流式输出
70
+ output: str = field(default="") # result or delta, 在流式里,我们也可能会输出 ResultContent 类型
71
+
72
+ def to_dict(self) -> Dict[str, Any]:
73
+ """Convert event metadata to dictionary for serialization"""
74
+ return self.model_dump()
75
+
76
+ def to_json(self) -> str:
77
+ """Convert event metadata to JSON string"""
78
+ return json.dumps(self.to_dict(),ensure_ascii=False)
79
+
80
+ @dataclass
81
+ class ResponseEvent(Event):
82
+ """
83
+ Event representing a response to another event.
84
+ """
85
+ response_to: str = field(default="") # event_id of the event this is responding to
86
+
87
+ def to_dict(self) -> Dict[str, Any]:
88
+ """Convert response event to dictionary for serialization"""
89
+ data = super().to_dict()
90
+ data["response_to"] = self.response_to
91
+ return data
92
+
93
+ @classmethod
94
+ def from_dict(cls, data: Dict[str, Any]) -> "ResponseEvent":
95
+ """Create response event from dictionary"""
96
+ event = super().from_dict(data)
97
+ return cls(
98
+ event_type=event.event_type,
99
+ event_id=event.event_id,
100
+ timestamp=event.timestamp,
101
+ content=event.content,
102
+ response_to=data.get("response_to", "")
103
+ )