weakincentives 0.3.0__py3-none-any.whl → 0.4.0__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 weakincentives might be problematic. Click here for more details.

@@ -44,14 +44,14 @@ _VFS_SECTION_TEMPLATE: Final[str] = (
44
44
  " specific files; keep listings focused to reduce output.\n"
45
45
  "2. Fetch file contents with `vfs_read_file` and work from the returned version"
46
46
  " to avoid conflicts.\n"
47
- "3. Create or update files with `vfs_write_file`; supply ASCII content up to"
47
+ "3. Create or update files with `vfs_write_file`; supply UTF-8 content up to"
48
48
  " 48k characters and prefer overwriting full files unless streaming append"
49
49
  " updates.\n"
50
50
  "4. Remove obsolete files or directories with `vfs_delete_entry` to keep the"
51
51
  " snapshot tidy.\n"
52
52
  "5. Host mounts are session-initialization only; agents cannot mount additional"
53
53
  " directories later.\n"
54
- "6. Avoid mirroring large repositories or binary assets—only ASCII text is"
54
+ "6. Avoid mirroring large repositories or binary assets—only UTF-8 text is"
55
55
  " accepted and host mounts remain constrained by their configuration."
56
56
  )
57
57
 
@@ -140,18 +140,19 @@ class VfsToolsSection(MarkdownSection[_VfsSectionParams]):
140
140
  allowed_roots = tuple(_normalize_root(path) for path in allowed_host_roots)
141
141
  session.register_reducer(VirtualFileSystem, replace_latest)
142
142
  mount_snapshot = _materialize_mounts(mounts, allowed_roots)
143
+ session.seed_slice(VirtualFileSystem, (mount_snapshot,))
143
144
  session.register_reducer(
144
145
  WriteFile,
145
- _make_write_reducer(mount_snapshot),
146
+ _make_write_reducer(),
146
147
  slice_type=VirtualFileSystem,
147
148
  )
148
149
  session.register_reducer(
149
150
  DeleteEntry,
150
- _make_delete_reducer(mount_snapshot),
151
+ _make_delete_reducer(),
151
152
  slice_type=VirtualFileSystem,
152
153
  )
153
154
 
154
- tools = _build_tools(session=session, mount_snapshot=mount_snapshot)
155
+ tools = _build_tools(session=session)
155
156
  super().__init__(
156
157
  title="Virtual Filesystem Tools",
157
158
  key="vfs.tools",
@@ -162,9 +163,9 @@ class VfsToolsSection(MarkdownSection[_VfsSectionParams]):
162
163
 
163
164
 
164
165
  def _build_tools(
165
- *, session: Session, mount_snapshot: VirtualFileSystem
166
+ *, session: Session
166
167
  ) -> tuple[Tool[SupportsDataclass, SupportsDataclass], ...]:
167
- suite = _VfsToolSuite(session=session, mount_snapshot=mount_snapshot)
168
+ suite = _VfsToolSuite(session=session)
168
169
  return (
169
170
  Tool[ListDirectory, ListDirectoryResult](
170
171
  name="vfs_list_directory",
@@ -192,9 +193,8 @@ def _build_tools(
192
193
  class _VfsToolSuite:
193
194
  """Collection of VFS handlers bound to a session instance."""
194
195
 
195
- def __init__(self, *, session: Session, mount_snapshot: VirtualFileSystem) -> None:
196
+ def __init__(self, *, session: Session) -> None:
196
197
  self._session = session
197
- self._mount_snapshot = mount_snapshot
198
198
 
199
199
  def list_directory(self, params: ListDirectory) -> ToolResult[ListDirectoryResult]:
200
200
  target = _normalize_optional_path(params.path)
@@ -229,9 +229,7 @@ class _VfsToolSuite:
229
229
  file = _find_file(snapshot.files, path)
230
230
  if file is None:
231
231
  raise ToolValidationError("File does not exist in the virtual filesystem.")
232
- message = (
233
- f"Read {file.size_bytes} bytes from {'/'.join(file.path.segments) or '.'}."
234
- )
232
+ message = _format_read_file_message(file)
235
233
  return ToolResult(message=message, value=file)
236
234
 
237
235
  def write_file(self, params: WriteFile) -> ToolResult[WriteFile]:
@@ -247,31 +245,27 @@ class _VfsToolSuite:
247
245
  if mode in {"overwrite", "append"} and existing is None:
248
246
  raise ToolValidationError("File does not exist for the requested mode.")
249
247
  normalized = WriteFile(path=path, content=content, mode=mode)
250
- action = {
251
- "create": "created",
252
- "overwrite": "overwritten",
253
- "append": "appended",
254
- }[mode]
255
- message = f"File {'/'.join(path.segments) or '.'} {action}."
248
+ message = _format_write_file_message(path, content, mode)
256
249
  return ToolResult(message=message, value=normalized)
257
250
 
258
251
  def delete_entry(self, params: DeleteEntry) -> ToolResult[DeleteEntry]:
259
252
  path = _normalize_path(params.path)
260
253
  snapshot = self._latest_snapshot()
261
- deleted_count = _count_matching(snapshot.files, path)
254
+ matches = tuple(
255
+ file
256
+ for file in snapshot.files
257
+ if _is_path_prefix(file.path.segments, path.segments)
258
+ )
259
+ deleted_count = len(matches)
262
260
  if deleted_count == 0:
263
261
  raise ToolValidationError("No files matched the provided path.")
264
262
  normalized = DeleteEntry(path=path)
265
- message = f"Deleted {deleted_count} entr{'ies' if deleted_count != 1 else 'y'}."
263
+ message = _format_delete_message(path, matches)
266
264
  return ToolResult(message=message, value=normalized)
267
265
 
268
266
  def _latest_snapshot(self) -> VirtualFileSystem:
269
267
  snapshot = select_latest(self._session, VirtualFileSystem)
270
- if snapshot is not None:
271
- return snapshot
272
- return (
273
- self._mount_snapshot if self._mount_snapshot.files else VirtualFileSystem()
274
- )
268
+ return snapshot or VirtualFileSystem()
275
269
 
276
270
 
277
271
  def _normalize_content(content: str) -> str:
@@ -279,7 +273,6 @@ def _normalize_content(content: str) -> str:
279
273
  raise ToolValidationError(
280
274
  "Content exceeds maximum length of 48,000 characters."
281
275
  )
282
- _ensure_ascii(content, "content")
283
276
  return content
284
277
 
285
278
 
@@ -352,24 +345,49 @@ def _is_path_prefix(path: Sequence[str], prefix: Sequence[str]) -> bool:
352
345
  return all(path[index] == prefix[index] for index in range(len(prefix)))
353
346
 
354
347
 
355
- def _count_matching(files: Iterable[VfsFile], path: VfsPath) -> int:
356
- target = path.segments
357
- return sum(1 for file in files if _is_path_prefix(file.path.segments, target))
358
-
359
-
360
348
  def _format_directory_message(
361
349
  path: VfsPath, directories: tuple[str, ...], files: tuple[str, ...]
362
350
  ) -> str:
363
- prefix = "/".join(path.segments) or "."
364
- directory_count = len(directories)
365
- file_count = len(files)
351
+ prefix = _format_path(path)
352
+ directory_list = ", ".join(directories) if directories else "(none)"
353
+ file_list = ", ".join(files) if files else "(none)"
354
+ return f"Directory {prefix}\nsubdirectories: {directory_list}\nfiles: {file_list}"
355
+
356
+
357
+ def _format_read_file_message(file: VfsFile) -> str:
358
+ path_label = _format_path(file.path)
359
+ header = (
360
+ f"Read file {path_label} (version {file.version}, {file.size_bytes} bytes)."
361
+ )
362
+ body = file.content if file.content else "(empty file)"
363
+ return f"{header}\n{body}"
364
+
365
+
366
+ def _format_write_file_message(path: VfsPath, content: str, mode: WriteMode) -> str:
367
+ path_label = _format_path(path)
368
+ action = {
369
+ "create": "create",
370
+ "overwrite": "overwrite",
371
+ "append": "append",
372
+ }[mode]
373
+ header = f"Staged {action} for {path_label} ({len(content)} characters)."
374
+ body = content if content else "(empty content)"
375
+ return f"{header}\n{body}"
376
+
377
+
378
+ def _format_delete_message(path: VfsPath, files: Sequence[VfsFile]) -> str:
379
+ path_label = _format_path(path)
380
+ deleted_paths = ", ".join(_format_path(file.path) for file in files)
366
381
  return (
367
- f"Directory {prefix} contains {directory_count} subdirector"
368
- f"{'ies' if directory_count != 1 else 'y'} and {file_count} file"
369
- f"{'s' if file_count != 1 else ''}."
382
+ f"Deleted {len(files)} entr{'ies' if len(files) != 1 else 'y'} under {path_label}:"
383
+ f" {deleted_paths}"
370
384
  )
371
385
 
372
386
 
387
+ def _format_path(path: VfsPath) -> str:
388
+ return "/".join(path.segments) or "."
389
+
390
+
373
391
  def _normalize_root(path: os.PathLike[str] | str) -> Path:
374
392
  root = Path(path).expanduser().resolve()
375
393
  if not root.exists():
@@ -425,7 +443,6 @@ def _load_mount(mount: HostMount, allowed_roots: Sequence[Path]) -> tuple[VfsFil
425
443
  content = path.read_text(encoding=_DEFAULT_ENCODING)
426
444
  except UnicodeDecodeError as error: # pragma: no cover - defensive guard
427
445
  raise ToolValidationError("Mounted file must be valid UTF-8.") from error
428
- _ensure_ascii(content, "mounted file content")
429
446
  size = len(content.encode(_DEFAULT_ENCODING))
430
447
  if mount.max_bytes is not None and consumed_bytes + size > mount.max_bytes:
431
448
  raise ToolValidationError("Host mount exceeded the configured byte budget.")
@@ -481,15 +498,13 @@ def _iter_mount_files(root: Path, follow_symlinks: bool) -> Iterable[Path]:
481
498
  yield current / name
482
499
 
483
500
 
484
- def _make_write_reducer(
485
- mount_snapshot: VirtualFileSystem,
486
- ) -> Callable[
501
+ def _make_write_reducer() -> Callable[
487
502
  [tuple[VirtualFileSystem, ...], DataEvent], tuple[VirtualFileSystem, ...]
488
503
  ]:
489
504
  def reducer(
490
505
  slice_values: tuple[VirtualFileSystem, ...], event: DataEvent
491
506
  ) -> tuple[VirtualFileSystem, ...]:
492
- previous = _latest_virtual_filesystem(slice_values, mount_snapshot)
507
+ previous = slice_values[-1] if slice_values else VirtualFileSystem()
493
508
  params = cast(WriteFile, event.value)
494
509
  timestamp = _now()
495
510
  files = list(previous.files)
@@ -527,15 +542,13 @@ def _make_write_reducer(
527
542
  return reducer
528
543
 
529
544
 
530
- def _make_delete_reducer(
531
- mount_snapshot: VirtualFileSystem,
532
- ) -> Callable[
545
+ def _make_delete_reducer() -> Callable[
533
546
  [tuple[VirtualFileSystem, ...], DataEvent], tuple[VirtualFileSystem, ...]
534
547
  ]:
535
548
  def reducer(
536
549
  slice_values: tuple[VirtualFileSystem, ...], event: DataEvent
537
550
  ) -> tuple[VirtualFileSystem, ...]:
538
- previous = _latest_virtual_filesystem(slice_values, mount_snapshot)
551
+ previous = slice_values[-1] if slice_values else VirtualFileSystem()
539
552
  params = cast(DeleteEntry, event.value)
540
553
  target = params.path.segments
541
554
  files = [
@@ -550,16 +563,6 @@ def _make_delete_reducer(
550
563
  return reducer
551
564
 
552
565
 
553
- def _latest_virtual_filesystem(
554
- values: tuple[VirtualFileSystem, ...], mount_snapshot: VirtualFileSystem
555
- ) -> VirtualFileSystem:
556
- if values:
557
- return values[-1]
558
- if mount_snapshot.files:
559
- return mount_snapshot
560
- return VirtualFileSystem()
561
-
562
-
563
566
  def _index_of(files: list[VfsFile], path: VfsPath) -> int | None:
564
567
  for index, file in enumerate(files):
565
568
  if file.path.segments == path.segments: