ommlds 0.0.0.dev447__py3-none-any.whl → 0.0.0.dev449__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 ommlds might be problematic. Click here for more details.

Files changed (29) hide show
  1. ommlds/.omlish-manifests.json +1 -1
  2. ommlds/cli/sessions/chat/prompt.py +8 -1
  3. ommlds/cli/sessions/chat/tools.py +14 -3
  4. ommlds/cli/tools/inject.py +4 -3
  5. ommlds/minichain/__init__.py +4 -0
  6. ommlds/minichain/backends/impls/google/stream.py +11 -2
  7. ommlds/minichain/lib/fs/binfiles.py +108 -0
  8. ommlds/minichain/lib/fs/catalog/ls.py +38 -0
  9. ommlds/minichain/lib/fs/catalog/read.py +115 -0
  10. ommlds/minichain/lib/fs/catalog/recursivels/__init__.py +0 -0
  11. ommlds/minichain/lib/fs/catalog/recursivels/execution.py +40 -0
  12. ommlds/minichain/lib/fs/context.py +112 -0
  13. ommlds/minichain/lib/fs/errors.py +95 -0
  14. ommlds/minichain/lib/fs/suggestions.py +36 -0
  15. ommlds/minichain/stream/services.py +18 -13
  16. ommlds/minichain/tools/execution/context.py +34 -14
  17. ommlds/minichain/tools/execution/errors.py +15 -0
  18. ommlds/minichain/tools/execution/reflect.py +0 -3
  19. ommlds/minichain/tools/reflect.py +40 -15
  20. {ommlds-0.0.0.dev447.dist-info → ommlds-0.0.0.dev449.dist-info}/METADATA +3 -3
  21. {ommlds-0.0.0.dev447.dist-info → ommlds-0.0.0.dev449.dist-info}/RECORD +28 -20
  22. ommlds/minichain/lib/fs/ls/execution.py +0 -32
  23. /ommlds/minichain/lib/fs/{ls → catalog}/__init__.py +0 -0
  24. /ommlds/minichain/lib/fs/{ls → catalog/recursivels}/rendering.py +0 -0
  25. /ommlds/minichain/lib/fs/{ls → catalog/recursivels}/running.py +0 -0
  26. {ommlds-0.0.0.dev447.dist-info → ommlds-0.0.0.dev449.dist-info}/WHEEL +0 -0
  27. {ommlds-0.0.0.dev447.dist-info → ommlds-0.0.0.dev449.dist-info}/entry_points.txt +0 -0
  28. {ommlds-0.0.0.dev447.dist-info → ommlds-0.0.0.dev449.dist-info}/licenses/LICENSE +0 -0
  29. {ommlds-0.0.0.dev447.dist-info → ommlds-0.0.0.dev449.dist-info}/top_level.txt +0 -0
@@ -138,7 +138,7 @@
138
138
  "module": ".minichain.backends.impls.google.stream",
139
139
  "attr": null,
140
140
  "file": "ommlds/minichain/backends/impls/google/stream.py",
141
- "line": 34,
141
+ "line": 36,
142
142
  "value": {
143
143
  "!.minichain.registries.manifests.RegistryManifest": {
144
144
  "module": "ommlds.minichain.backends.impls.google.stream",
@@ -1,4 +1,5 @@
1
1
  import dataclasses as dc
2
+ import os
2
3
 
3
4
  from omlish import check
4
5
  from omlish import lang
@@ -125,7 +126,13 @@ class PromptChatSession(ChatSession['PromptChatSession.Config']):
125
126
 
126
127
  tr: mc.ToolExecRequest = check.single(check.not_none(trs))
127
128
 
128
- trm = await self._tool_exec_request_executor.execute_tool_request(tr)
129
+ # FIXME: lol
130
+ from ....minichain.lib.fs.context import FsToolContext
131
+
132
+ trm = await self._tool_exec_request_executor.execute_tool_request(
133
+ tr,
134
+ FsToolContext(root_dir=os.getcwd()),
135
+ )
129
136
 
130
137
  print(trm.c)
131
138
  new_chat.append(trm)
@@ -63,7 +63,11 @@ class AskingToolExecutionConfirmation(ToolExecutionConfirmation):
63
63
 
64
64
  class ToolExecRequestExecutor(lang.Abstract):
65
65
  @abc.abstractmethod
66
- def execute_tool_request(self, tr: mc.ToolExecRequest) -> ta.Awaitable[mc.ToolExecResultMessage]:
66
+ def execute_tool_request(
67
+ self,
68
+ tr: mc.ToolExecRequest,
69
+ *ctx_items: ta.Any,
70
+ ) -> ta.Awaitable[mc.ToolExecResultMessage]:
67
71
  raise NotImplementedError
68
72
 
69
73
 
@@ -79,13 +83,20 @@ class ToolExecRequestExecutorImpl(ToolExecRequestExecutor):
79
83
  self._catalog = catalog
80
84
  self._confirmation = confirmation
81
85
 
82
- async def execute_tool_request(self, tr: mc.ToolExecRequest) -> mc.ToolExecResultMessage:
86
+ async def execute_tool_request(
87
+ self,
88
+ tr: mc.ToolExecRequest,
89
+ *ctx_items: ta.Any,
90
+ ) -> mc.ToolExecResultMessage:
83
91
  tce = self._catalog.by_name[check.non_empty_str(tr.name)]
84
92
 
85
93
  await self._confirmation.confirm_tool_execution_or_raise(tr, tce)
86
94
 
87
95
  return await mc.execute_tool_request(
88
- mc.ToolContext(),
96
+ mc.ToolContext(
97
+ tr,
98
+ *ctx_items,
99
+ ),
89
100
  tce.executor(),
90
101
  tr,
91
102
  )
@@ -47,13 +47,14 @@ def bind_tools(tools_config: ToolsConfig) -> inj.Elements:
47
47
  #
48
48
 
49
49
  if tools_config.enable_fs_tools:
50
- from ...minichain.lib.fs.ls.execution import ls_tool
51
-
50
+ from ...minichain.lib.fs.catalog.ls import ls_tool
52
51
  els.append(bind_tool(ls_tool()))
53
52
 
53
+ from ...minichain.lib.fs.catalog.read import read_tool
54
+ els.append(bind_tool(read_tool()))
55
+
54
56
  if tools_config.enable_unsafe_bash_tool:
55
57
  from ...minichain.lib.bash import bash_tool
56
-
57
58
  els.append(bind_tool(bash_tool()))
58
59
 
59
60
  if tools_config.enable_test_weather_tool:
@@ -361,6 +361,10 @@ with _lang.auto_proxy_init(
361
361
  reflect_tool_catalog_entry,
362
362
  )
363
363
 
364
+ from .tools.execution.errors import ( # noqa
365
+ ToolExecutionError,
366
+ )
367
+
364
368
  from .tools.fns import ( # noqa
365
369
  ToolFn,
366
370
 
@@ -19,7 +19,9 @@ from ....chat.messages import UserMessage
19
19
  from ....chat.stream.services import ChatChoicesStreamRequest
20
20
  from ....chat.stream.services import ChatChoicesStreamResponse
21
21
  from ....chat.stream.services import static_check_is_chat_choices_stream_service
22
+ from ....chat.stream.types import AiChoiceDelta
22
23
  from ....chat.stream.types import AiChoiceDeltas
24
+ from ....chat.stream.types import AiMessageDelta
23
25
  from ....models.configs import ModelName
24
26
  from ....resources import UseResources
25
27
  from ....standard import ApiKey
@@ -64,6 +66,8 @@ class GoogleChatChoicesStreamService:
64
66
  AiMessage: 'assistant',
65
67
  }
66
68
 
69
+ READ_CHUNK_SIZE = 64 * 1024
70
+
67
71
  async def invoke(
68
72
  self,
69
73
  request: ChatChoicesStreamRequest,
@@ -87,7 +91,7 @@ class GoogleChatChoicesStreamService:
87
91
  model_name = MODEL_NAMES.resolve(self._model_name.v)
88
92
 
89
93
  http_request = http.HttpRequest(
90
- f'{self.BASE_URL.rstrip("/")}/{model_name}:generateContent?key={key}',
94
+ f'{self.BASE_URL.rstrip("/")}/{model_name}:streamGenerateContent?alt=sse&key={key}',
91
95
  headers={'Content-Type': 'application/json'},
92
96
  data=json.dumps_compact(req_dct).encode('utf-8'),
93
97
  method='POST',
@@ -111,6 +115,11 @@ class GoogleChatChoicesStreamService:
111
115
  continue
112
116
  if l.startswith('data: '):
113
117
  gcr = msh.unmarshal(json.loads(l[6:]), pt.GenerateContentResponse) # noqa
114
- await sink.emit([])
118
+ cnd = check.single(check.not_none(gcr.candidates))
119
+ for p in check.not_none(cnd.content).parts or []:
120
+ await sink.emit([AiChoiceDelta(AiMessageDelta(check.not_none(p.text)))])
121
+
122
+ if not b:
123
+ return []
115
124
 
116
125
  return await new_stream_response(rs, inner)
@@ -0,0 +1,108 @@
1
+ import os.path
2
+ import typing as ta
3
+
4
+
5
+ ##
6
+
7
+
8
+ IMAGE_FILE_EXTENSIONS: ta.AbstractSet[str] = frozenset([
9
+ 'jpg',
10
+ 'jpeg',
11
+ 'png',
12
+ 'gif',
13
+ 'bmp',
14
+ 'webp',
15
+ ])
16
+
17
+
18
+ ARCHIVE_FILE_EXTENSIONS: ta.AbstractSet[str] = frozenset([
19
+ 'zip',
20
+ 'tar',
21
+ 'gz',
22
+ 'xz',
23
+ '7z',
24
+ ])
25
+
26
+
27
+ DOC_FILE_EXTENSIONS: ta.AbstractSet[str] = frozenset([
28
+ 'doc',
29
+ 'docx',
30
+ 'xls',
31
+ 'xlsx',
32
+ 'ppt',
33
+ 'pptx',
34
+ 'odt',
35
+ 'ods',
36
+ 'odp',
37
+ ])
38
+
39
+
40
+ EXECUTABLE_FILE_EXTENSIONS: ta.AbstractSet[str] = frozenset([
41
+ 'exe',
42
+ 'dll',
43
+ 'so',
44
+
45
+ 'obj',
46
+ 'o',
47
+ 'a',
48
+ 'lib',
49
+
50
+ 'class',
51
+ 'jar',
52
+ 'war',
53
+
54
+ 'wasm',
55
+
56
+ 'pyc',
57
+ 'pyo',
58
+ ])
59
+
60
+
61
+ BLOB_FILE_EXTENSIONS: ta.AbstractSet[str] = frozenset([
62
+ 'bin',
63
+ 'dat',
64
+ ])
65
+
66
+
67
+ BINARY_FILE_EXTENSIONS: ta.AbstractSet[str] = frozenset([
68
+ *IMAGE_FILE_EXTENSIONS,
69
+ *ARCHIVE_FILE_EXTENSIONS,
70
+ *DOC_FILE_EXTENSIONS,
71
+ *EXECUTABLE_FILE_EXTENSIONS,
72
+ *BLOB_FILE_EXTENSIONS,
73
+ ])
74
+
75
+
76
+ def has_binary_file_extension(file_path: str) -> bool:
77
+ return os.path.basename(file_path).partition('.')[-1] in BINARY_FILE_EXTENSIONS
78
+
79
+
80
+ ##
81
+
82
+
83
+ def is_binary_file(
84
+ file_path: str,
85
+ *,
86
+ chunk_size: int = 0x1000,
87
+ non_printable_cutoff: float = .3,
88
+
89
+ st: os.stat_result | None = None,
90
+ ) -> bool:
91
+ if st is None:
92
+ try:
93
+ st = os.stat(file_path)
94
+ except OSError:
95
+ return False
96
+
97
+ if not st.st_size:
98
+ return False
99
+
100
+ with open(file_path, 'rb') as f:
101
+ chunk = f.read(chunk_size)
102
+
103
+ if 0 in chunk:
104
+ return True
105
+
106
+ # Count "non-printable" ASCII-ish control chars (excluding TAB/LF/CR)
107
+ np = sum(1 for b in chunk if b < 9 or (13 < b < 32))
108
+ return (np / len(chunk)) > non_printable_cutoff
@@ -0,0 +1,38 @@
1
+ import io
2
+ import os
3
+
4
+ from omlish import lang
5
+
6
+ from ....tools.execution.catalog import ToolCatalogEntry
7
+ from ....tools.execution.reflect import reflect_tool_catalog_entry
8
+ from ..context import fs_tool_context
9
+
10
+
11
+ ##
12
+
13
+
14
+ def execute_ls_tool(
15
+ dir_path: str,
16
+ ) -> str:
17
+ """
18
+ Lists the contents of the specified dir.
19
+
20
+ Args:
21
+ dir_path: The dir to list the contents of. Must be an absolute path.
22
+ """
23
+
24
+ ft_ctx = fs_tool_context()
25
+ ft_ctx.check_stat_dir(dir_path)
26
+
27
+ out = io.StringIO()
28
+ out.write('<dir>\n')
29
+ for e in sorted(os.scandir(dir_path), key=lambda e: e.name): # noqa
30
+ out.write(f'{e.name}{"/" if e.is_dir() else ""}\n')
31
+ out.write('</dir>\n')
32
+
33
+ return out.getvalue()
34
+
35
+
36
+ @lang.cached_function
37
+ def ls_tool() -> ToolCatalogEntry:
38
+ return reflect_tool_catalog_entry(execute_ls_tool)
@@ -0,0 +1,115 @@
1
+ """
2
+ TODO:
3
+ - better bad unicode handling
4
+ - read whole file if < some size, report filesize / num lines / mtime inline
5
+ - fs cache
6
+ - track changes
7
+ """
8
+ import io
9
+ import itertools
10
+
11
+ from omlish import lang
12
+
13
+ from ....tools.execution.catalog import ToolCatalogEntry
14
+ from ....tools.execution.reflect import reflect_tool_catalog_entry
15
+ from ....tools.reflect import tool_spec_override
16
+ from ....tools.types import ToolParam
17
+ from ..context import fs_tool_context
18
+
19
+
20
+ ##
21
+
22
+
23
+ DEFAULT_MAX_NUM_LINES = 2_000
24
+
25
+ MAX_LINE_LENGTH = 2_000
26
+
27
+
28
+ @tool_spec_override(
29
+ desc=rf"""
30
+ Reads a file from the local filesystem. You can access any file directly by using this tool.
31
+
32
+ Assume this tool is able to read all files on the machine. If the User provides a path to a file assume that
33
+ path is valid. It is okay to read a file that does not exist; an error will be returned.
34
+
35
+ Usage:
36
+ - The file_path parameter must be an absolute path, not a relative path.
37
+ - By default, it reads up to {DEFAULT_MAX_NUM_LINES} lines starting from the beginning of the file.
38
+ - You can optionally specify a line offset and limit (especially handy for long files), but it's recommended to
39
+ read the whole file by not providing these parameters.
40
+ - Any lines longer than {MAX_LINE_LENGTH} characters will be truncated with "...".
41
+ - Invalid unicode characters will be replaced with the unicode replacement character "\\ufffd".
42
+ - Results are returned using cat -n format, with line numbers starting at 1 and suffixed with a pipe character
43
+ "|".
44
+ - This tool cannot read binary files, including images.
45
+ """,
46
+ params=[
47
+ ToolParam(
48
+ 'file_path',
49
+ desc='The absolute path to the file to read.',
50
+ ),
51
+ ToolParam(
52
+ 'line_offset',
53
+ desc='The line number to start reading from (0-based).',
54
+ ),
55
+ ToolParam(
56
+ 'num_lines',
57
+ desc=f'The number of lines to read (defaults to {DEFAULT_MAX_NUM_LINES}).',
58
+ ),
59
+ ],
60
+ )
61
+ def execute_read_tool(
62
+ file_path: str,
63
+ *,
64
+ line_offset: int = 0,
65
+ num_lines: int = DEFAULT_MAX_NUM_LINES,
66
+ ) -> str:
67
+ ft_ctx = fs_tool_context()
68
+ ft_ctx.check_stat_file(file_path, text=True)
69
+
70
+ out = io.StringIO()
71
+ out.write('<file>\n')
72
+
73
+ zp = len(str(line_offset + num_lines))
74
+ n = line_offset
75
+ has_trunc = False # noqa
76
+ with open(file_path, errors='replace') as f:
77
+ fi = iter(f)
78
+
79
+ for line in itertools.islice(fi, line_offset, line_offset + num_lines):
80
+ out.write(f'{str(n + 1).zfill(zp):}|')
81
+ line = line.removesuffix('\n')
82
+ if len(line) > MAX_LINE_LENGTH:
83
+ has_trunc = True # noqa
84
+ out.write(line[:MAX_LINE_LENGTH])
85
+ out.write('...')
86
+ else:
87
+ out.write(line)
88
+ out.write('\n')
89
+ n += 1
90
+
91
+ # tl = n
92
+ # if (ml := lang.ilen(fi)):
93
+ # check.state(n == num_lines)
94
+ # tl += ml
95
+
96
+ try:
97
+ next(fi)
98
+ except StopIteration:
99
+ has_more = False
100
+ else:
101
+ has_more = True
102
+
103
+ out.write(f'</file>\n')
104
+
105
+ if has_more:
106
+ out.write(
107
+ f'\n(File has more lines. Use "line_offset" parameter to read beyond line {line_offset + num_lines}.)\n',
108
+ )
109
+
110
+ return out.getvalue()
111
+
112
+
113
+ @lang.cached_function
114
+ def read_tool() -> ToolCatalogEntry:
115
+ return reflect_tool_catalog_entry(execute_read_tool)
@@ -0,0 +1,40 @@
1
+ from omlish import lang
2
+
3
+ from .....tools.execution.catalog import ToolCatalogEntry
4
+ from .....tools.execution.reflect import reflect_tool_catalog_entry
5
+ from ...context import fs_tool_context
6
+ from .rendering import LsLinesRenderer
7
+ from .running import LsRunner
8
+
9
+
10
+ ##
11
+
12
+
13
+ def execute_recursive_ls_tool(
14
+ base_path: str,
15
+ ) -> str:
16
+ """
17
+ Recursively lists the directory contents of the given base path.
18
+
19
+ Args:
20
+ base_path: The path of the directory to list the contents of. Must be absolute.
21
+
22
+ Returns:
23
+ A formatted string of the recursive directory contents.
24
+ """
25
+
26
+ ft_ctx = fs_tool_context()
27
+ ft_ctx.check_requested_path(base_path)
28
+
29
+ root = LsRunner().run(base_path)
30
+ lines = LsLinesRenderer().render(root)
31
+ return '\n'.join([
32
+ '<dir>',
33
+ *lines.lines,
34
+ '</dir>',
35
+ ])
36
+
37
+
38
+ @lang.cached_function
39
+ def recursive_ls_tool() -> ToolCatalogEntry:
40
+ return reflect_tool_catalog_entry(execute_recursive_ls_tool)
@@ -0,0 +1,112 @@
1
+ import os.path
2
+ import stat
3
+
4
+ from omlish import check
5
+
6
+ from ...tools.execution.context import tool_context
7
+ from .binfiles import has_binary_file_extension
8
+ from .binfiles import is_binary_file
9
+ from .errors import RequestedPathDoesNotExistError
10
+ from .errors import RequestedPathOutsideRootDirError
11
+ from .errors import RequestedPathWrongTypeError
12
+ from .suggestions import get_path_suggestions
13
+
14
+
15
+ ##
16
+
17
+
18
+ class FsToolContext:
19
+ def __init__(
20
+ self,
21
+ *,
22
+ root_dir: str | None = None,
23
+ ) -> None:
24
+ super().__init__()
25
+
26
+ self._root_dir = root_dir
27
+ self._abs_root_dir = os.path.abspath(root_dir) if root_dir is not None else None
28
+
29
+ #
30
+
31
+ def check_requested_path(self, req_path: str) -> None:
32
+ abs_req_path = os.path.abspath(req_path)
33
+
34
+ if (
35
+ self._abs_root_dir is None or
36
+ not (
37
+ abs_req_path == self._abs_root_dir or
38
+ abs_req_path.startswith(self._abs_root_dir + os.path.sep)
39
+ )
40
+ ):
41
+ raise RequestedPathOutsideRootDirError(
42
+ req_path,
43
+ root_dir=check.not_none(self._root_dir),
44
+ )
45
+
46
+ #
47
+
48
+ def check_stat_dir(
49
+ self,
50
+ req_path: str,
51
+ ) -> os.stat_result:
52
+ self.check_requested_path(req_path)
53
+
54
+ try:
55
+ st = os.stat(req_path)
56
+ except FileNotFoundError:
57
+ raise RequestedPathDoesNotExistError(
58
+ req_path,
59
+ suggested_paths=get_path_suggestions(
60
+ req_path,
61
+ filter=lambda e: e.is_dir(),
62
+ ),
63
+ ) from None
64
+
65
+ if not stat.S_ISDIR(st.st_mode):
66
+ raise RequestedPathWrongTypeError(
67
+ req_path,
68
+ expected_type='dir',
69
+ **(dict(actual_type='file') if stat.S_ISREG(st.st_mode) else {}),
70
+ )
71
+
72
+ return st
73
+
74
+ def check_stat_file(
75
+ self,
76
+ req_path: str,
77
+ *,
78
+ text: bool = False,
79
+ ) -> os.stat_result:
80
+ self.check_requested_path(req_path)
81
+
82
+ try:
83
+ st = os.stat(req_path)
84
+ except FileNotFoundError:
85
+ raise RequestedPathDoesNotExistError(
86
+ req_path,
87
+ suggested_paths=get_path_suggestions(
88
+ req_path,
89
+ filter=lambda e: (e.is_file() and not has_binary_file_extension(e.name)),
90
+ ),
91
+ ) from None
92
+
93
+ if not stat.S_ISREG(st.st_mode):
94
+ is_dir = stat.S_ISDIR(st.st_mode)
95
+ raise RequestedPathWrongTypeError(
96
+ req_path,
97
+ expected_type='file',
98
+ **(dict(actual_type='dir') if is_dir else {}),
99
+ )
100
+
101
+ if text and is_binary_file(req_path, st=st):
102
+ raise RequestedPathWrongTypeError(
103
+ req_path,
104
+ expected_type='text file',
105
+ actual_type='binary file',
106
+ )
107
+
108
+ return st
109
+
110
+
111
+ def fs_tool_context() -> FsToolContext:
112
+ return tool_context()[FsToolContext]
@@ -0,0 +1,95 @@
1
+ import typing as ta
2
+
3
+ from omlish import lang
4
+
5
+ from ...tools.execution.errors import ToolExecutionError
6
+
7
+
8
+ ##
9
+
10
+
11
+ class FsToolExecutionError(ToolExecutionError, lang.Abstract):
12
+ pass
13
+
14
+
15
+ ##
16
+
17
+
18
+ class RequestedPathError(FsToolExecutionError, lang.Abstract):
19
+ def __init__(self, requested_path: str, *args: ta.Any) -> None:
20
+ super().__init__(requested_path, *args)
21
+
22
+ self.requested_path = requested_path
23
+
24
+
25
+ class RequestedPathMustBeAbsoluteError(RequestedPathError):
26
+ @property
27
+ def content(self) -> str:
28
+ return f'Requested path {self.requested_path!r} must be absolute.'
29
+
30
+
31
+ class RequestedPathOutsideRootDirError(RequestedPathError):
32
+ def __init__(
33
+ self,
34
+ requested_path: str,
35
+ *,
36
+ root_dir: str,
37
+ ) -> None:
38
+ super().__init__(
39
+ requested_path,
40
+ root_dir,
41
+ )
42
+
43
+ self.root_dir = root_dir
44
+
45
+ @property
46
+ def content(self) -> str:
47
+ return f'Requested path {self.requested_path!r} was outside of permitted root directory {self.root_dir!r}.'
48
+
49
+
50
+ class RequestedPathWrongTypeError(RequestedPathError):
51
+ def __init__(
52
+ self,
53
+ requested_path: str,
54
+ *,
55
+ expected_type: str,
56
+ actual_type: str | None = None,
57
+ ) -> None:
58
+ super().__init__(
59
+ requested_path,
60
+ expected_type,
61
+ actual_type,
62
+ )
63
+
64
+ self.expected_type = expected_type
65
+ self.actual_type = actual_type
66
+
67
+ @property
68
+ def content(self) -> str:
69
+ return ''.join([
70
+ f'Requested path {self.requested_path!r} must be of type {self.expected_type!r}',
71
+ *([f', but it is actually of type {self.actual_type!r}'] if self.actual_type is not None else []),
72
+ '.',
73
+ ])
74
+
75
+
76
+ class RequestedPathDoesNotExistError(RequestedPathError):
77
+ def __init__(
78
+ self,
79
+ requested_path: str,
80
+ *,
81
+ suggested_paths: ta.Sequence[str] | None = None,
82
+ ) -> None:
83
+ super().__init__(
84
+ requested_path,
85
+ suggested_paths,
86
+ )
87
+
88
+ self.suggested_paths = suggested_paths
89
+
90
+ @property
91
+ def content(self) -> str:
92
+ return ''.join([
93
+ f'Requested path {self.requested_path!r} does not exist.',
94
+ *([f' Did you mean one of these valid paths: {self.suggested_paths!r}?'] if self.suggested_paths else []),
95
+ ])
@@ -0,0 +1,36 @@
1
+ import difflib
2
+ import os.path
3
+ import typing as ta
4
+
5
+
6
+ ##
7
+
8
+
9
+ def get_path_suggestions(
10
+ bad_path: str,
11
+ n: int = 3,
12
+ *,
13
+ filter: ta.Callable[[os.DirEntry], bool] | None = None, # noqa
14
+ cutoff: float = .6,
15
+ ) -> ta.Sequence[str] | None:
16
+ dn = os.path.dirname(bad_path)
17
+ try:
18
+ sdi = os.scandir(dn)
19
+ except FileNotFoundError:
20
+ return None
21
+
22
+ fl = [
23
+ e.name
24
+ for e in sdi
25
+ if (filter is None or filter(e))
26
+ ]
27
+
28
+ return [
29
+ os.path.join(dn, sn)
30
+ for sn in difflib.get_close_matches(
31
+ os.path.basename(bad_path),
32
+ fl,
33
+ n,
34
+ cutoff=cutoff,
35
+ )
36
+ ]
@@ -1,5 +1,6 @@
1
1
  import abc
2
2
  import itertools
3
+ import types
3
4
  import typing as ta
4
5
 
5
6
  from omlish import check
@@ -40,15 +41,11 @@ class StreamResponseSink(lang.Abstract, ta.Generic[V]):
40
41
  raise NotImplementedError
41
42
 
42
43
 
43
- class StreamResponseIterator(lang.Abstract, ta.Generic[V, OutputT]):
44
- @abc.abstractmethod
45
- def __aenter__(self) -> ta.Awaitable[ta.Self]:
46
- raise NotImplementedError
47
-
48
- @abc.abstractmethod
49
- def __aexit__(self, exc_type, exc_val, exc_tb) -> ta.Awaitable[None]:
50
- raise NotImplementedError
51
-
44
+ class StreamResponseIterator(
45
+ ta.AsyncContextManager['StreamResponseIterator[V, OutputT]'],
46
+ lang.Abstract,
47
+ ta.Generic[V, OutputT],
48
+ ):
52
49
  @property
53
50
  @abc.abstractmethod
54
51
  def outputs(self) -> tv.TypedValues[OutputT]:
@@ -120,7 +117,8 @@ class _StreamServiceResponse(StreamResponseIterator[V, OutputT]):
120
117
  self._g = iter(self._a)
121
118
  return self
122
119
 
123
- async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
120
+ @types.coroutine
121
+ def _aexit(self, exc_type, exc_val, exc_tb):
124
122
  old_state = self._state
125
123
  self._state = 'closed'
126
124
  if old_state != 'running':
@@ -137,7 +135,7 @@ class _StreamServiceResponse(StreamResponseIterator[V, OutputT]):
137
135
  if cex2 is cex:
138
136
  break
139
137
  raise
140
- await x
138
+ yield x
141
139
  if self._cr.cr_running:
142
140
  raise RuntimeError(f'Coroutine {self._cr!r} not terminated')
143
141
  if self._g is not self._a:
@@ -145,13 +143,17 @@ class _StreamServiceResponse(StreamResponseIterator[V, OutputT]):
145
143
  self._a.close()
146
144
  self._cr.close()
147
145
 
146
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
147
+ return await self._aexit(exc_type, exc_val, exc_tb)
148
+
148
149
  _outputs: tv.TypedValues[OutputT]
149
150
 
150
151
  @property
151
152
  def outputs(self) -> tv.TypedValues[OutputT]:
152
153
  return self._outputs
153
154
 
154
- async def __anext__(self) -> V:
155
+ @types.coroutine
156
+ def _anext(self):
155
157
  check.state(self._state == 'running')
156
158
  while True:
157
159
  try:
@@ -168,7 +170,10 @@ class _StreamServiceResponse(StreamResponseIterator[V, OutputT]):
168
170
  x.done = True
169
171
  return x.value
170
172
 
171
- await x
173
+ yield x
174
+
175
+ async def __anext__(self) -> V:
176
+ return await self._anext()
172
177
 
173
178
 
174
179
  ##
@@ -2,8 +2,8 @@ import contextlib
2
2
  import contextvars
3
3
  import typing as ta
4
4
 
5
+ from omlish import check
5
6
  from omlish import collections as col
6
- from omlish import dataclasses as dc
7
7
  from omlish import lang
8
8
 
9
9
 
@@ -13,30 +13,34 @@ T = ta.TypeVar('T')
13
13
  ##
14
14
 
15
15
 
16
- @dc.dataclass(frozen=True)
17
16
  class ToolContext(lang.Final):
18
- dct: col.TypeMap = col.TypeMap()
17
+ def __init__(self, *items: ta.Any) -> None:
18
+ super().__init__()
19
19
 
20
- @classmethod
21
- def new(cls, *objs: ta.Any) -> 'ToolContext':
22
- return cls(col.TypeMap(objs))
20
+ self._dct: col.TypeMap = col.TypeMap(items)
21
+ if ToolContext in self._dct:
22
+ raise KeyError(ToolContext)
23
23
 
24
- #
24
+ def __repr__(self) -> str:
25
+ return f'{self.__class__.__name__}<{", ".join(ic.__name__ for ic in self._dct)}>'
25
26
 
26
27
  def __len__(self) -> int:
27
- return len(self.dct)
28
+ return len(self._dct)
28
29
 
29
30
  def __iter__(self) -> ta.Iterator[ta.Any]:
30
- return iter(self.dct)
31
+ return iter(self._dct)
32
+
33
+ def __contains__(self, ty: type[T]) -> bool:
34
+ return ty in self._dct
31
35
 
32
36
  def get(self, ty: type[T]) -> T | None:
33
- return self.dct.get(ty)
37
+ return self._dct.get(ty)
34
38
 
35
39
  def __getitem__(self, cls: type[T]) -> T:
36
- return self.dct[cls]
40
+ return self._dct[cls]
37
41
 
38
42
  def get_any(self, cls: type | tuple[type, ...]) -> ta.Sequence[T]:
39
- return self.dct.get_any(cls)
43
+ return self._dct.get_any(cls)
40
44
 
41
45
 
42
46
  ##
@@ -45,8 +49,24 @@ class ToolContext(lang.Final):
45
49
  _TOOL_CONTEXT: contextvars.ContextVar[ToolContext] = contextvars.ContextVar(f'{__name__}._TOOL_CONTEXT')
46
50
 
47
51
 
48
- @contextlib.contextmanager
49
- def bind_tool_context(ctx: ToolContext) -> ta.Generator[ToolContext]:
52
+ @ta.overload
53
+ def bind_tool_context(ctx: ToolContext) -> ta.ContextManager[ToolContext]:
54
+ ...
55
+
56
+
57
+ @ta.overload
58
+ def bind_tool_context(*items: ta.Any) -> ta.ContextManager[ToolContext]:
59
+ ...
60
+
61
+
62
+ @contextlib.contextmanager # type: ignore[misc]
63
+ def bind_tool_context(*args):
64
+ if args and isinstance(args[0], ToolContext):
65
+ check.arg(len(args) == 1)
66
+ ctx = args[0]
67
+ else:
68
+ ctx = ToolContext(*args)
69
+
50
70
  try:
51
71
  cur = _TOOL_CONTEXT.get()
52
72
  except LookupError:
@@ -0,0 +1,15 @@
1
+ import abc
2
+
3
+ from omlish import lang
4
+
5
+ from ...content.materialize import CanContent
6
+
7
+
8
+ ##
9
+
10
+
11
+ class ToolExecutionError(Exception, lang.Abstract):
12
+ @property
13
+ @abc.abstractmethod
14
+ def content(self) -> CanContent:
15
+ raise NotImplementedError
@@ -1,6 +1,3 @@
1
- """
2
- TODO:
3
- """
4
1
  import inspect
5
2
  import typing as ta
6
3
 
@@ -23,6 +23,7 @@ from omlish import metadata as md
23
23
  from omlish import reflect as rfl
24
24
  from omlish.lite.cached import cached_nullary
25
25
 
26
+ from ..content.materialize import CanContent
26
27
  from .types import EnumToolDtype
27
28
  from .types import MappingToolDtype
28
29
  from .types import NullableToolDtype
@@ -160,14 +161,22 @@ class ToolReflector:
160
161
 
161
162
  #
162
163
 
164
+ p_ovr_dct: dict[str, dict[str, ta.Any]] = {}
163
165
  ts_ovr: dict[str, ta.Any] = {}
166
+ o: _ToolSpecOverride
164
167
  for o in md.get_object_metadata(fn, type=_ToolSpecOverride):
165
- # TODO: better params handling / merging
166
168
  ts_ovr.update({
167
169
  k: v
168
170
  for k, v in dc.asdict(o).items()
169
- if v is not None
171
+ if k != 'params'
172
+ and v is not None
170
173
  })
174
+ for op in (o.params or []):
175
+ p_ovr_dct.setdefault(check.non_empty_str(op.name), {}).update({
176
+ k: v
177
+ for k, v in dc.asdict(op).items()
178
+ if v is not None
179
+ })
171
180
 
172
181
  #
173
182
 
@@ -207,23 +216,39 @@ class ToolReflector:
207
216
  if 'params' not in ts_kw:
208
217
  ds_p_dct = {
209
218
  ds_p.arg_name: ds_p
210
- for ds_p in (ds.params if ds is not None else [])
219
+ for ds_p in (ds.params if ds is not None else {})
211
220
  }
212
221
 
213
- params: dict[str, ToolParam] = {}
214
- for sig_p in sig().parameters.values():
215
- check.not_in(sig_p.name, params)
216
-
217
- ds_p = ds_p_dct.get(sig_p.name)
218
-
219
- params[sig_p.name] = ToolParam(
220
- sig_p.name,
222
+ sig_p_dct = sig().parameters
221
223
 
222
- desc=self._prepare_desc(ds_p.description) if ds_p is not None else None,
224
+ pns: list[str] = list({**p_ovr_dct, **ds_p_dct, **sig_p_dct})
223
225
 
224
- type=self.reflect_type(rfl.type_(th()[sig_p.name])) if sig_p.name in th() else None,
225
-
226
- required=sig_p.default is inspect.Parameter.empty,
226
+ params: dict[str, ToolParam] = {}
227
+ for pn in pns:
228
+ ovr_p = p_ovr_dct.get(pn, {})
229
+ ds_p = ds_p_dct.get(pn)
230
+ sig_p = sig_p_dct.get(pn)
231
+
232
+ p_desc: CanContent
233
+ if (p_desc := ovr_p.get('desc')) is None:
234
+ if ds_p is not None:
235
+ p_desc = ds_p.description
236
+
237
+ p_type: ToolDtype | None
238
+ if (p_type := ovr_p.get('type')) is None:
239
+ if sig_p is not None and sig_p.name in th():
240
+ p_type = self.reflect_type(rfl.type_(th()[sig_p.name]))
241
+
242
+ p_required: bool | None
243
+ if (p_required := ovr_p.get('required')) is None:
244
+ if sig_p is not None:
245
+ p_required = sig_p.default is inspect.Parameter.empty
246
+
247
+ params[pn] = ToolParam(
248
+ pn,
249
+ desc=self._prepare_desc(p_desc) if p_desc is not None else None,
250
+ type=p_type,
251
+ required=p_required,
227
252
  )
228
253
 
229
254
  ts_kw.update(params=tuple(params.values()) if params else None)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ommlds
3
- Version: 0.0.0.dev447
3
+ Version: 0.0.0.dev449
4
4
  Summary: ommlds
5
5
  Author: wrmsr
6
6
  License-Expression: BSD-3-Clause
@@ -14,8 +14,8 @@ Classifier: Programming Language :: Python :: 3.13
14
14
  Requires-Python: >=3.13
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
- Requires-Dist: omdev==0.0.0.dev447
18
- Requires-Dist: omlish==0.0.0.dev447
17
+ Requires-Dist: omdev==0.0.0.dev449
18
+ Requires-Dist: omlish==0.0.0.dev449
19
19
  Provides-Extra: all
20
20
  Requires-Dist: llama-cpp-python~=0.3; extra == "all"
21
21
  Requires-Dist: mlx~=0.29; extra == "all"
@@ -1,4 +1,4 @@
1
- ommlds/.omlish-manifests.json,sha256=0nHO21Xk5kpNbvdLvitrGGWPsn0baEhGD9I835QE_Es,17984
1
+ ommlds/.omlish-manifests.json,sha256=s92cmVTNvGiopN6a0kMNziKvMkcePcRspm8dsIL6_SM,17984
2
2
  ommlds/__about__.py,sha256=Z9VIVQnuNBbIYEtIm9XZU4T2QGRqMNjtmQX2OOTaUc0,1759
3
3
  ommlds/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  ommlds/huggingface.py,sha256=JfEyfKOxU3-SY_ojtXBJFNeD-NIuKjvMe3GL3e93wNA,1175
@@ -89,21 +89,21 @@ ommlds/cli/sessions/chat/base.py,sha256=GlDBXZ-KymSokNl1a7uwX1abswmRE67Q0zOIHjhZ
89
89
  ommlds/cli/sessions/chat/inject.py,sha256=zg26F-yDm9sH4COzS-3yZua2taeb3ogHCOZ34UEKI54,2661
90
90
  ommlds/cli/sessions/chat/interactive.py,sha256=0Pt3H7uZl9nGXAvMqGxU8Ih65N-DrWSkLahRFmnkFw8,1984
91
91
  ommlds/cli/sessions/chat/printing.py,sha256=urlxuSBOisRVn1f_uDb343pyBps9ht2BznMWB_H0ti8,2321
92
- ommlds/cli/sessions/chat/prompt.py,sha256=nEj2vwzwBJ4WTEwXwKkygrOktqqsoyGaJ46z-LYl1pQ,4547
92
+ ommlds/cli/sessions/chat/prompt.py,sha256=y6R12CasYWx2o4zE74uIV77dBQomU85ie4v9xN5FOYM,4754
93
93
  ommlds/cli/sessions/chat/state.py,sha256=en29yekINGqVqqMoqZxxIRMuU_j3Z7Qxvtr8q5igcgg,2621
94
- ommlds/cli/sessions/chat/tools.py,sha256=wEcpawpEj2Zgti9gWGjk_B9yfCM3SL-tFVb56cQnDso,2259
94
+ ommlds/cli/sessions/chat/tools.py,sha256=VBylmlKyoHcjFgK2rN391ZL9SvVNBqDj-3IrqKM-wNQ,2446
95
95
  ommlds/cli/sessions/completion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
96
96
  ommlds/cli/sessions/completion/completion.py,sha256=2ZrzmHBCF3mG13ABcoiHva6OUzPpdF7qzByteaLNsxk,1077
97
97
  ommlds/cli/sessions/embedding/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
98
  ommlds/cli/sessions/embedding/embedding.py,sha256=9U5Maj-XI5nsrk7m-O3P2rZggey0z2p7onZn2QfQe38,1051
99
99
  ommlds/cli/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
100
100
  ommlds/cli/tools/config.py,sha256=fjdZAxs9r7aNN-YjYLQNU15KWOUtL5azX_3typf-ltQ,225
101
- ommlds/cli/tools/inject.py,sha256=dnS9OdYAGRE2QpWGAge3bu3UCmc7h5EUXVhnwL2zJsk,1447
101
+ ommlds/cli/tools/inject.py,sha256=gaoBXour9MJCWSnoX9iPB9KJGSXdON7OLqnqovpx9F4,1550
102
102
  ommlds/cli/tools/weather.py,sha256=FUPxPF5KBbbyK3voBtcRUOkuMO2zK5GFZoFTJMaM6LQ,300
103
103
  ommlds/datasets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
104
104
  ommlds/datasets/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
105
105
  ommlds/datasets/lib/movies.py,sha256=LmdfoXsZU9XMM_r-sxCLv_s06BFzwWO4xUj6sc9XVcI,1961
106
- ommlds/minichain/__init__.py,sha256=6R-TOiDSx4PuK8_57XUWVmpG1bfzDx8pZxHSQWpMUtc,10400
106
+ ommlds/minichain/__init__.py,sha256=aYCJoi85KolsqQ-UdtHcwDLy0Y0qHpnBdQaiQZH1ZoI,10485
107
107
  ommlds/minichain/_marshal.py,sha256=Lcib21J6jWyd-fkdQhUzij-TH2tnwylBChLRGrinWbk,892
108
108
  ommlds/minichain/_typedvalues.py,sha256=vPwVYGWtIJT_dclarnZlA-2sjP2o3L2HSDK5VDurYfM,2229
109
109
  ommlds/minichain/completion.py,sha256=lQ0LfCIYZsvDqteHhhDIv16D2_gn_xMfEL0ouywE5Yo,1033
@@ -133,7 +133,7 @@ ommlds/minichain/backends/impls/google/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JC
133
133
  ommlds/minichain/backends/impls/google/chat.py,sha256=Dq_PrkRt07dN5A3UPj0qwB3IMIS9caAQff6076q6kUQ,3192
134
134
  ommlds/minichain/backends/impls/google/names.py,sha256=HxHJ31HeKZg6aW1C_Anqp-gamCXpq9pOdKj8_yVgE8Y,871
135
135
  ommlds/minichain/backends/impls/google/search.py,sha256=5-2nAZ1QmbqHSQcwWnqqcgCM-Duy2ryctJEIv2tcpZg,3260
136
- ommlds/minichain/backends/impls/google/stream.py,sha256=3XWZwYFQrA8U_QIexjJkjaWfLo2lfLbF_x7DsqcNRF4,4313
136
+ ommlds/minichain/backends/impls/google/stream.py,sha256=8DbgK61HRovW1dq_VBXYDnjU7oVFsE2DmCfLLHoznNI,4736
137
137
  ommlds/minichain/backends/impls/huggingface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
138
138
  ommlds/minichain/backends/impls/huggingface/configs.py,sha256=6jsBtPNXOP57PcpxNTVLGWLc-18Iwn_lDbGouwCJTIQ,258
139
139
  ommlds/minichain/backends/impls/huggingface/repos.py,sha256=8BDxJmra9elSQL2vzp2nr2p4Hpq56A3zTk7hTTnfJU4,861
@@ -216,10 +216,17 @@ ommlds/minichain/docs/filters.py,sha256=rIW_x4QR7P2a2sqsEDw0xWJOKtK670i_bWfMZ52J
216
216
  ommlds/minichain/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
217
217
  ommlds/minichain/lib/bash.py,sha256=a7HUzb0KOCPRjJuWuJW6IGUxcF53Zx13q6huHO_82nw,872
218
218
  ommlds/minichain/lib/fs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
219
- ommlds/minichain/lib/fs/ls/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
220
- ommlds/minichain/lib/fs/ls/execution.py,sha256=hwUTAwqwjSjH99hyH9EqzrnlXtv3u5ySt-QLWSZjAtY,753
221
- ommlds/minichain/lib/fs/ls/rendering.py,sha256=ZeeQA7o59Kg9Ili8icpBzlFMMUHvCejERSBnqpqRYPo,4296
222
- ommlds/minichain/lib/fs/ls/running.py,sha256=s52tal84PRvF2X8spvyFkKC8jHjBJbJMKSkriAXzt98,1601
219
+ ommlds/minichain/lib/fs/binfiles.py,sha256=_N5REnCrscISObrYieO9mKD82b_0NoWSHlfMS8C5ksA,1742
220
+ ommlds/minichain/lib/fs/context.py,sha256=slZk7NmTdXj-nugRFO3gW1wb2vRzy193yUsROrK3NpA,3087
221
+ ommlds/minichain/lib/fs/errors.py,sha256=Iz_UedMiBLlN3qWH1dIh-ztOP4iA_qc0nEcOQ27nTUQ,2430
222
+ ommlds/minichain/lib/fs/suggestions.py,sha256=mpWTr1XgXyt0XEp1kEFnC7CEhxKB80Z3t6C7WdUYt24,687
223
+ ommlds/minichain/lib/fs/catalog/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
224
+ ommlds/minichain/lib/fs/catalog/ls.py,sha256=H9pc04P33mdNb77eRF74OtDFj5Q7ln5jelVVT6ZOkXo,836
225
+ ommlds/minichain/lib/fs/catalog/read.py,sha256=vDrlVEgc-kxOl5r9fZ4l4EK-PoghijOOXcB47YnUR2s,3538
226
+ ommlds/minichain/lib/fs/catalog/recursivels/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
227
+ ommlds/minichain/lib/fs/catalog/recursivels/execution.py,sha256=0-yak1nesT_yPL__TQ5fMI9t-o63-htfsGt8dZKulIE,970
228
+ ommlds/minichain/lib/fs/catalog/recursivels/rendering.py,sha256=ZeeQA7o59Kg9Ili8icpBzlFMMUHvCejERSBnqpqRYPo,4296
229
+ ommlds/minichain/lib/fs/catalog/recursivels/running.py,sha256=s52tal84PRvF2X8spvyFkKC8jHjBJbJMKSkriAXzt98,1601
223
230
  ommlds/minichain/llms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
224
231
  ommlds/minichain/llms/_marshal.py,sha256=oxx5mLPl0KtcqFWOhdz0H5Nk9C27lDPAdOVOk8mPStE,1463
225
232
  ommlds/minichain/llms/tokens.py,sha256=RPrAzf4Qx9xNPGj7_EkzcVOR9qIGkhQg8AM6qhr7gfw,292
@@ -242,7 +249,7 @@ ommlds/minichain/services/requests.py,sha256=VAfKbYu4T0CZTWVQmZ2LUmYU7DNm6IerYMN
242
249
  ommlds/minichain/services/responses.py,sha256=4W6Z4Fx4_GFqKgle27OeLr0zzjVTA0pkZrlsZiFQNdo,1534
243
250
  ommlds/minichain/services/services.py,sha256=WjkQNYIp87SflLSReOHMkG2qIVAOem6vsrs_2NxWN_M,325
244
251
  ommlds/minichain/stream/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
245
- ommlds/minichain/stream/services.py,sha256=OQ-CjCM8iKYReTg8aj0VK3XzZLdRude86kg6FNr-wew,5279
252
+ ommlds/minichain/stream/services.py,sha256=fFR1klP_PZJ3Pqmqx_SGap8gRDuthJah1fyoke6G9Ww,5328
246
253
  ommlds/minichain/stream/wrap.py,sha256=nQC0aCi49I18nF0Yx8qiiLkhIAECV6s6o4pvOy5Kx98,2041
247
254
  ommlds/minichain/text/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
248
255
  ommlds/minichain/text/applypatch.py,sha256=YIN5JChJ0FXyK1I6OiAHQmE7BT-exHfaAMM9ay7ylyc,17705
@@ -264,13 +271,14 @@ ommlds/minichain/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
264
271
  ommlds/minichain/tools/_marshal.py,sha256=CATmXmiDHB6cWgJngd3jE1ZcHK7M4Hwqa2KsBDvhpjo,411
265
272
  ommlds/minichain/tools/fns.py,sha256=4GA4N0x1BMSaEMS3QnyfpFi6FHWCPOvEbya_JNPfUR8,3119
266
273
  ommlds/minichain/tools/jsonschema.py,sha256=qKpDgD8IS6R-QLXbgU8DNpiS4bgHQBp7k1QGblAObDo,4029
267
- ommlds/minichain/tools/reflect.py,sha256=0II-ZtDtxhaM5CX56de-J1dcs7CQ8TUxULXmEv2qhr4,7340
274
+ ommlds/minichain/tools/reflect.py,sha256=81664ngwoWOppJ6UgNMfPW7_GPkKGf-kf1v4lIhZN-8,8353
268
275
  ommlds/minichain/tools/types.py,sha256=V5EMfsuKuXOthLZzm75uu61wq4o2P-D9wuhKvG-JtGk,4050
269
276
  ommlds/minichain/tools/execution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
270
277
  ommlds/minichain/tools/execution/catalog.py,sha256=xjAgbbkVdjEuA44geaDMNE-KYfATJ-tPaI-jcTB8O9g,1961
271
- ommlds/minichain/tools/execution/context.py,sha256=nkYV03ZYbxAFy8I48oMT3y9_5nGayUevVxeTDlWsydw,1322
278
+ ommlds/minichain/tools/execution/context.py,sha256=Gdl1UNjzLQTeIc7m2BlNyLtNsdqCookQv12_WwDDkAI,1872
279
+ ommlds/minichain/tools/execution/errors.py,sha256=-fE4yh-PsKQqYMyZW0CZMZIjXkJHLZAYyl3j2VYkJ-U,251
272
280
  ommlds/minichain/tools/execution/executors.py,sha256=UPQh-aljQyLYmv-qf2bpVSYMlvjHUOystDEry3Z2WT8,1265
273
- ommlds/minichain/tools/execution/reflect.py,sha256=IGMFotVBJwBl8i-hK_bkkJZ4C8vVnhud5J43eMHmk0k,814
281
+ ommlds/minichain/tools/execution/reflect.py,sha256=VQ_6YTnnHDPZowVdEa8RgELUFKKVNCaL-kdv41v8z_s,800
274
282
  ommlds/minichain/vectors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
275
283
  ommlds/minichain/vectors/_marshal.py,sha256=lMyPX2qC6Ri0E1BZZOP7R4E009eoIS6TZKbqSe_nTos,1490
276
284
  ommlds/minichain/vectors/embeddings.py,sha256=_r5DcCaTI-we_XLAHcPv-1PsKI-i-ndptn_qOJ9_fbc,1000
@@ -300,9 +308,9 @@ ommlds/wiki/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
300
308
  ommlds/wiki/utils/io.py,sha256=UKgDJGtmpnWvIqVd2mJc2QNPOqlToEY1GEveNp6_pMo,7088
301
309
  ommlds/wiki/utils/progress.py,sha256=EhvKcMFYtsarCQhIahlO6f0SboyAKP3UwUyrnVnP-Vk,3222
302
310
  ommlds/wiki/utils/xml.py,sha256=vVV8Ctn13aaRM9eYfs9Wd6rHn5WOCEUzQ44fIhOvJdg,3754
303
- ommlds-0.0.0.dev447.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
304
- ommlds-0.0.0.dev447.dist-info/METADATA,sha256=a2muFyUNAsNLhxd5yCTZTUo1ZwX7RpjX8HI03hB57Lc,3224
305
- ommlds-0.0.0.dev447.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
306
- ommlds-0.0.0.dev447.dist-info/entry_points.txt,sha256=Z5YWtX7ClfiCKdW-dd_CSVvM0h4yQpJPi-2G3q6gNFo,35
307
- ommlds-0.0.0.dev447.dist-info/top_level.txt,sha256=Rbnk5d5wi58vnAXx13WFZqdQ4VX8hBCS2hEL3WeXOhY,7
308
- ommlds-0.0.0.dev447.dist-info/RECORD,,
311
+ ommlds-0.0.0.dev449.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
312
+ ommlds-0.0.0.dev449.dist-info/METADATA,sha256=E-__wcJOkcxnzvGP5f5PsJWc3NGQ0Fw_aL6d4D2kqhU,3224
313
+ ommlds-0.0.0.dev449.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
314
+ ommlds-0.0.0.dev449.dist-info/entry_points.txt,sha256=Z5YWtX7ClfiCKdW-dd_CSVvM0h4yQpJPi-2G3q6gNFo,35
315
+ ommlds-0.0.0.dev449.dist-info/top_level.txt,sha256=Rbnk5d5wi58vnAXx13WFZqdQ4VX8hBCS2hEL3WeXOhY,7
316
+ ommlds-0.0.0.dev449.dist-info/RECORD,,
@@ -1,32 +0,0 @@
1
- from omlish import lang
2
-
3
- from ....tools.execution.catalog import ToolCatalogEntry
4
- from ....tools.execution.reflect import reflect_tool_catalog_entry
5
- from .rendering import LsLinesRenderer
6
- from .running import LsRunner
7
-
8
-
9
- ##
10
-
11
-
12
- def execute_ls_tool(
13
- base_path: str,
14
- ) -> str:
15
- """
16
- Recursively lists the directory contents of the given base path.
17
-
18
- Args:
19
- base_path: The path of the directory to list the contents of.
20
-
21
- Returns:
22
- A formatted string of the recursive directory contents.
23
- """
24
-
25
- root = LsRunner().run(base_path)
26
- lines = LsLinesRenderer().render(root)
27
- return '\n'.join(lines.lines)
28
-
29
-
30
- @lang.cached_function
31
- def ls_tool() -> ToolCatalogEntry:
32
- return reflect_tool_catalog_entry(execute_ls_tool)
File without changes