auto-coder 0.1.302__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.
- {auto_coder-0.1.302.dist-info → auto_coder-0.1.303.dist-info}/METADATA +2 -1
- {auto_coder-0.1.302.dist-info → auto_coder-0.1.303.dist-info}/RECORD +28 -20
- autocoder/auto_coder.py +5 -3
- autocoder/auto_coder_runner.py +7 -7
- autocoder/chat_auto_coder.py +6 -1
- autocoder/commands/auto_command.py +285 -206
- autocoder/commands/tools.py +123 -85
- autocoder/common/__init__.py +2 -0
- autocoder/common/action_yml_file_manager.py +22 -0
- autocoder/common/auto_configure.py +9 -4
- autocoder/common/stream_out_type.py +7 -0
- autocoder/dispacher/actions/action.py +221 -101
- autocoder/dispacher/actions/plugins/action_regex_project.py +18 -0
- autocoder/events/__init__.py +57 -0
- autocoder/events/event_content.py +423 -0
- autocoder/events/event_manager.py +327 -0
- autocoder/events/event_manager_singleton.py +245 -0
- autocoder/events/event_store.py +376 -0
- autocoder/events/event_types.py +103 -0
- autocoder/index/entry.py +88 -60
- autocoder/index/filter/quick_filter.py +71 -3
- autocoder/run_context.py +62 -0
- autocoder/utils/auto_coder_utils/chat_stream_out.py +32 -1
- autocoder/version.py +1 -1
- {auto_coder-0.1.302.dist-info → auto_coder-0.1.303.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.302.dist-info → auto_coder-0.1.303.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.302.dist-info → auto_coder-0.1.303.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.302.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
|
+
)
|