ommlds 0.0.0.dev450__py3-none-any.whl → 0.0.0.dev451__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 (49) hide show
  1. ommlds/.omlish-manifests.json +3 -3
  2. ommlds/backends/anthropic/protocol/_marshal.py +1 -1
  3. ommlds/backends/anthropic/protocol/sse/_marshal.py +1 -1
  4. ommlds/backends/anthropic/protocol/sse/assemble.py +1 -1
  5. ommlds/backends/anthropic/protocol/types.py +30 -9
  6. ommlds/backends/google/protocol/_marshal.py +1 -1
  7. ommlds/backends/openai/protocol/_marshal.py +1 -1
  8. ommlds/cli/main.py +2 -2
  9. ommlds/cli/sessions/chat/code.py +12 -2
  10. ommlds/cli/sessions/chat/printing.py +4 -0
  11. ommlds/cli/sessions/chat/tools.py +1 -2
  12. ommlds/cli/tools/config.py +1 -1
  13. ommlds/cli/tools/inject.py +4 -1
  14. ommlds/minichain/__init__.py +12 -0
  15. ommlds/minichain/_marshal.py +39 -0
  16. ommlds/minichain/backends/impls/anthropic/chat.py +78 -10
  17. ommlds/minichain/backends/impls/google/chat.py +39 -11
  18. ommlds/minichain/chat/_marshal.py +1 -1
  19. ommlds/minichain/content/_marshal.py +24 -3
  20. ommlds/minichain/content/json.py +13 -0
  21. ommlds/minichain/content/materialize.py +13 -20
  22. ommlds/minichain/content/prepare.py +4 -0
  23. ommlds/minichain/json.py +20 -0
  24. ommlds/minichain/lib/fs/context.py +15 -1
  25. ommlds/minichain/lib/fs/errors.py +6 -0
  26. ommlds/minichain/lib/fs/tools/edit.py +104 -0
  27. ommlds/minichain/lib/fs/tools/ls.py +2 -2
  28. ommlds/minichain/lib/fs/tools/read.py +2 -2
  29. ommlds/minichain/lib/fs/tools/recursivels/execution.py +2 -2
  30. ommlds/minichain/lib/todo/context.py +29 -2
  31. ommlds/minichain/lib/todo/tools/read.py +11 -6
  32. ommlds/minichain/lib/todo/tools/write.py +73 -13
  33. ommlds/minichain/lib/todo/types.py +6 -1
  34. ommlds/minichain/llms/_marshal.py +1 -1
  35. ommlds/minichain/services/_marshal.py +1 -1
  36. ommlds/minichain/tools/_marshal.py +1 -1
  37. ommlds/minichain/tools/execution/catalog.py +2 -1
  38. ommlds/minichain/tools/execution/executors.py +8 -3
  39. ommlds/minichain/tools/execution/reflect.py +43 -5
  40. ommlds/minichain/tools/fns.py +46 -9
  41. ommlds/minichain/tools/reflect.py +2 -2
  42. ommlds/minichain/vectors/_marshal.py +1 -1
  43. ommlds/tools/ocr.py +7 -1
  44. {ommlds-0.0.0.dev450.dist-info → ommlds-0.0.0.dev451.dist-info}/METADATA +3 -3
  45. {ommlds-0.0.0.dev450.dist-info → ommlds-0.0.0.dev451.dist-info}/RECORD +49 -47
  46. {ommlds-0.0.0.dev450.dist-info → ommlds-0.0.0.dev451.dist-info}/WHEEL +0 -0
  47. {ommlds-0.0.0.dev450.dist-info → ommlds-0.0.0.dev451.dist-info}/entry_points.txt +0 -0
  48. {ommlds-0.0.0.dev450.dist-info → ommlds-0.0.0.dev451.dist-info}/licenses/LICENSE +0 -0
  49. {ommlds-0.0.0.dev450.dist-info → ommlds-0.0.0.dev451.dist-info}/top_level.txt +0 -0
@@ -1,15 +1,17 @@
1
1
  """
2
2
  TODO:
3
- - ContentNamespace
3
+ - ContentNamespace Example materializable class
4
4
  """
5
5
  import typing as ta
6
6
 
7
7
  from omlish import lang
8
+ from omlish import marshal as msh
8
9
 
10
+ from ....content.namespaces import ContentNamespace
9
11
  from ....tools.execution.catalog import ToolCatalogEntry
10
12
  from ....tools.execution.reflect import reflect_tool_catalog_entry
11
13
  from ....tools.reflect import tool_spec_override
12
- from ..context import todo_tool_context
14
+ from ..context import tool_todo_context
13
15
  from ..types import TODO_ITEM_FIELD_DESCS
14
16
  from ..types import TodoItem
15
17
 
@@ -17,36 +19,54 @@ from ..types import TodoItem
17
19
  ##
18
20
 
19
21
 
20
- @tool_spec_override(
21
- desc=f"""
22
+ class TodoWriteDescriptionChunks(ContentNamespace):
23
+ INTRO: ta.ClassVar[str] = """
22
24
  Use this tool to create and manage a structured list of todo items corresponding to subtasks for your current
23
25
  session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user. It
24
26
  also helps the user understand the progress of the overall task and progress of their requests.
27
+ """
25
28
 
29
+ FIELD_DESCRIPTIONS: ta.ClassVar[str] = f"""
26
30
  A todo item is comprised of the following fields:
27
31
  - id: {TODO_ITEM_FIELD_DESCS['id']}
28
32
  - content: {TODO_ITEM_FIELD_DESCS['content']}
29
33
  - priority: {TODO_ITEM_FIELD_DESCS['priority']}
30
34
  - status: {TODO_ITEM_FIELD_DESCS['status']}
31
-
32
- Todo item priorities are as follows:
35
+ """
36
+
37
+ WHEN_USING_THIS_TOOL: ta.ClassVar[str] = """
38
+ When using the todo write tool:
39
+ - All items must be present on each use of the tool.
40
+ - All fields except the id field must be present on all items. If not given, the id field will be automatically
41
+ set to a valid integer.
42
+ - The new todo list, including any generated ids, will be returned from the tool.
43
+ """
44
+
45
+ STATUS_DESCRIPTIONS: ta.ClassVar[str] = """
46
+ Todo item statuses are as follows:
33
47
  - pending: Task not yet started.
34
48
  - in_progress: Currently working on (limit to ONE task at a time).
35
49
  - completed: Task finished successfully.
36
50
  - cancelled: Task no longer needed.
51
+ """
37
52
 
53
+ MANAGING_ITEMS: ta.ClassVar[str] = """
38
54
  Manage todo items in the following manner:
39
55
  - Update todo item status in real-time as you work.
40
56
  - Mark todo items complete IMMEDIATELY after finishing (don't batch completions).
41
57
  - Only have ONE todo item task in_progress at any time.
42
58
  - Complete current todo item tasks before starting new ones.
43
59
  - Cancel todo items that become irrelevant.
60
+ """
44
61
 
62
+ BREAKING_DOWN: ta.ClassVar[str] = """
45
63
  Breakdown tasks in the following manner:
46
64
  - Create specific, actionable items.
47
65
  - Break complex tasks into smaller, manageable steps.
48
66
  - Use clear, descriptive task names.
67
+ """
49
68
 
69
+ USE_PROACTIVELY: ta.ClassVar[str] = """
50
70
  Use this tool proactively in these scenarios:
51
71
  - Complex multi-step tasks - When a task requires 3 or more distinct steps or actions.
52
72
  - Non-trivial and complex tasks - Tasks that require careful planning or multiple operations.
@@ -57,18 +77,26 @@ from ..types import TodoItem
57
77
  - After completing a task - Mark it complete and add any new follow-up tasks
58
78
  - When you start working on a new task, mark the todo item as in_progress. Ideally you should only have one todo
59
79
  item as in_progress at a time. Complete existing tasks before starting new ones.
80
+ """
60
81
 
82
+ SKIP_USE_WHEN: ta.ClassVar[str] = """
61
83
  Skip using this tool when:
62
84
  - There is only a single, straightforward task.
63
85
  - The task is trivial and tracking it provides no organizational benefit.
64
86
  - The task can be completed in less than 3 trivial steps.
65
87
  - The task is purely conversational or informational.
88
+ """
66
89
 
90
+ SKIP_WHEN_TRIVIAL: ta.ClassVar[str] = """
67
91
  You should not use this tool if there is only one trivial task to do. In this case you are better off just doing
68
92
  the task directly.
93
+ """
69
94
 
95
+ EXAMPLES_WHEN_HEADER: ta.ClassVar[str] = """
70
96
  ## Examples of WHEN to use the todo list
97
+ """
71
98
 
99
+ EXAMPLE_WHEN_1: ta.ClassVar[str] = """
72
100
  <example>
73
101
  <user>
74
102
  I want to add a dark mode toggle to the application settings. Make sure you run the tests and build when
@@ -96,7 +124,9 @@ from ..types import TodoItem
96
124
  the final task.
97
125
  </reasoning>
98
126
  </example>
127
+ """
99
128
 
129
+ EXAMPLE_WHEN_2: ta.ClassVar[str] = """
100
130
  <example>
101
131
  <user>
102
132
  Help me rename the function getCwd to getCurrentWorkingDirectory across my project
@@ -123,7 +153,9 @@ from ..types import TodoItem
123
153
  - This approach prevents missing any occurrences and maintains code consistency.
124
154
  </reasoning>
125
155
  </example>
156
+ """
126
157
 
158
+ EXAMPLE_WHEN_3: ta.ClassVar[str] = """
127
159
  <example>
128
160
  <user>
129
161
  I need to implement these features for my e-commerce site: user registration, product catalog, shopping
@@ -146,7 +178,9 @@ from ..types import TodoItem
146
178
  - This approach allows for tracking progress across the entire implementation.
147
179
  </reasoning>
148
180
  </example>
181
+ """
149
182
 
183
+ EXAMPLE_WHEN_4: ta.ClassVar[str] = """
150
184
  <example>
151
185
  <user>
152
186
  Can you help optimize my React application? It's rendering slowly and has performance issues.
@@ -182,9 +216,13 @@ from ..types import TodoItem
182
216
  - This systematic approach ensures all performance bottlenecks are addressed.
183
217
  </reasoning>
184
218
  </example>
219
+ """
185
220
 
221
+ EXAMPLES_WHEN_NOT_HEADER: ta.ClassVar[str] = """
186
222
  ## Examples of when NOT to use the todo list
223
+ """
187
224
 
225
+ EXAMPLE_WHEN_NOT_1: ta.ClassVar[str] = """
188
226
  <example>
189
227
  <user>
190
228
  How do I print 'Hello World' in Python?
@@ -200,8 +238,10 @@ from ..types import TodoItem
200
238
  The assistant did not use the todo list because this is a single, trivial task that can be completed in
201
239
  one step. There's no need to track multiple tasks or steps for such a straightforward request.
202
240
  </reasoning>
203
- </counter>
241
+ </example>
242
+ """
204
243
 
244
+ EXAMPLE_WHEN_NOT_2: ta.ClassVar[str] = """
205
245
  <example>
206
246
  <user>
207
247
  What does the git status command do?
@@ -217,7 +257,9 @@ from ..types import TodoItem
217
257
  multiple steps or tasks.
218
258
  </reasoning>
219
259
  </example>
260
+ """
220
261
 
262
+ EXAMPLE_WHEN_NOT_3: ta.ClassVar[str] = """
221
263
  <example>
222
264
  <user>
223
265
  Can you add a comment to the calculateTotal function to explain what it does?
@@ -234,7 +276,9 @@ from ..types import TodoItem
234
276
  organization.
235
277
  </reasoning>
236
278
  </example>
279
+ """
237
280
 
281
+ EXAMPLE_WHEN_NOT_4: ta.ClassVar[str] = """
238
282
  <example>
239
283
  <user>
240
284
  Run npm install for me and tell me what happens.
@@ -258,18 +302,34 @@ from ..types import TodoItem
258
302
  straightforward task.
259
303
  </reasoning>
260
304
  </example>
305
+ """
261
306
 
307
+ OUTRO: ta.ClassVar[str] = """
262
308
  When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you
263
309
  complete all requirements successfully.
264
- """,
310
+ """
311
+
312
+
313
+ @tool_spec_override(
314
+ desc=TodoWriteDescriptionChunks,
265
315
  )
266
- def execute_todo_write_tool(todo_items: ta.Sequence[TodoItem]) -> str:
267
- ctx = todo_tool_context()
268
- ctx.set_items(todo_items)
316
+ def execute_todo_write_tool(todo_items: ta.Sequence[TodoItem]) -> ta.Sequence[TodoItem]:
317
+ if todo_items:
318
+ todo_items = [
319
+ msh.unmarshal(o, TodoItem) if not isinstance(o, TodoItem) else o # noqa
320
+ for o in todo_items
321
+ ]
322
+
323
+ ctx = tool_todo_context()
324
+ out_items = ctx.set_items(todo_items)
269
325
 
270
- return 'Todo list updated.'
326
+ return out_items or []
271
327
 
272
328
 
273
329
  @lang.cached_function
274
330
  def todo_write_tool() -> ToolCatalogEntry:
275
- return reflect_tool_catalog_entry(execute_todo_write_tool)
331
+ return reflect_tool_catalog_entry(
332
+ execute_todo_write_tool,
333
+ marshal_input=True,
334
+ marshal_output=True,
335
+ )
@@ -38,7 +38,12 @@ TOOL_PRIORITIES: ta.Sequence[str] = str_literal_values(ToolPriority)
38
38
 
39
39
  @dc.dataclass(frozen=True, kw_only=True)
40
40
  class TodoItem(lang.Final):
41
- id: str = dc.field(metadata=tool_param_metadata(desc='A unique identifier for this todo item.'))
41
+ id: str | None = dc.field(default=None, metadata=tool_param_metadata(
42
+ desc=(
43
+ 'A unique identifier for this todo item within the current session. '
44
+ 'If this is not provided it will be automatically set to an integer.'
45
+ ),
46
+ ))
42
47
  content: str = dc.field(metadata=tool_param_metadata(desc='A brief description of the task.'))
43
48
  priority: ToolPriority = dc.field(metadata=tool_param_metadata(
44
49
  desc=f'Priority of the task: {join_human_readable_str_list(map(repr, TOOL_PRIORITIES))}.',
@@ -42,7 +42,7 @@ class TokensUnmarshalerFactory(msh.SimpleUnmarshalerFactory):
42
42
 
43
43
 
44
44
  @lang.static_init
45
- def _install_standard_marshalling() -> None:
45
+ def _install_standard_marshaling() -> None:
46
46
  msh.install_standard_factories(
47
47
  TokensMarshalerFactory(),
48
48
  TokensUnmarshalerFactory(),
@@ -134,7 +134,7 @@ class _RequestResponseUnmarshalerFactory(msh.SimpleUnmarshalerFactory):
134
134
 
135
135
 
136
136
  @lang.static_init
137
- def _install_standard_marshalling() -> None:
137
+ def _install_standard_marshaling() -> None:
138
138
  msh.install_standard_factories(
139
139
  _RequestResponseMarshalerFactory(),
140
140
  _RequestResponseUnmarshalerFactory(),
@@ -8,7 +8,7 @@ from .types import ToolDtype
8
8
 
9
9
 
10
10
  @lang.static_init
11
- def _install_standard_marshalling() -> None:
11
+ def _install_standard_marshaling() -> None:
12
12
  tool_dtype_poly = msh.polymorphism_from_subclasses(ToolDtype, naming=msh.Naming.SNAKE)
13
13
  msh.install_standard_factories(
14
14
  msh.PolymorphismMarshalerFactory(tool_dtype_poly),
@@ -4,6 +4,7 @@ from omlish import check
4
4
  from omlish import dataclasses as dc
5
5
  from omlish import lang
6
6
 
7
+ from ...content.types import Content
7
8
  from ..fns import ToolFn
8
9
  from ..types import ToolSpec
9
10
  from .context import ToolContext
@@ -68,7 +69,7 @@ class ToolCatalog(ToolExecutor):
68
69
  ctx: ToolContext,
69
70
  name: str,
70
71
  args: ta.Mapping[str, ta.Any],
71
- ) -> str:
72
+ ) -> Content:
72
73
  e = self._by_name[name]
73
74
 
74
75
  return await e.executor().execute_tool(
@@ -1,9 +1,14 @@
1
+ """
2
+ TODO:
3
+ - args is Mapping[str, Content] too
4
+ """
1
5
  import abc
2
6
  import typing as ta
3
7
 
4
8
  from omlish import dataclasses as dc
5
9
  from omlish import lang
6
10
 
11
+ from ...content.types import Content
7
12
  from ..fns import ToolFn
8
13
  from ..fns import execute_tool_fn
9
14
  from .context import ToolContext
@@ -20,7 +25,7 @@ class ToolExecutor(lang.Abstract):
20
25
  ctx: ToolContext,
21
26
  name: str,
22
27
  args: ta.Mapping[str, ta.Any],
23
- ) -> ta.Awaitable[str]:
28
+ ) -> ta.Awaitable[Content]:
24
29
  raise NotImplementedError
25
30
 
26
31
 
@@ -36,7 +41,7 @@ class ToolFnToolExecutor(ToolExecutor):
36
41
  ctx: ToolContext,
37
42
  name: str,
38
43
  args: ta.Mapping[str, ta.Any],
39
- ) -> str:
44
+ ) -> Content:
40
45
  with bind_tool_context(ctx):
41
46
  return await execute_tool_fn(
42
47
  self.tool_fn,
@@ -56,5 +61,5 @@ class NameSwitchedToolExecutor(ToolExecutor):
56
61
  ctx: ToolContext,
57
62
  name: str,
58
63
  args: ta.Mapping[str, ta.Any],
59
- ) -> str:
64
+ ) -> Content:
60
65
  return await self.tool_executors_by_name[name].execute_tool(ctx, name, args)
@@ -1,7 +1,13 @@
1
+ """
2
+ TODO:
3
+ - ta.type_hints
4
+ """
1
5
  import inspect
2
6
  import typing as ta
3
7
 
4
8
  from omlish import lang
9
+ from omlish import marshal as msh
10
+ from omlish import reflect as rfl
5
11
 
6
12
  from ..fns import ToolFn
7
13
  from ..reflect import reflect_tool_spec
@@ -11,7 +17,13 @@ from .catalog import ToolCatalogEntry
11
17
  ##
12
18
 
13
19
 
14
- def reflect_tool_catalog_entry(fn: ta.Callable) -> ToolCatalogEntry:
20
+ def reflect_tool_catalog_entry(
21
+ fn: ta.Callable,
22
+ *,
23
+ marshal_input: bool = False,
24
+ marshal_output: bool = False,
25
+ no_marshal_check: bool = False,
26
+ ) -> ToolCatalogEntry:
15
27
  impl: ToolFn.Impl
16
28
  if lang.is_maysync(fn):
17
29
  impl = ToolFn.MaysyncImpl(fn)
@@ -20,15 +32,41 @@ def reflect_tool_catalog_entry(fn: ta.Callable) -> ToolCatalogEntry:
20
32
  else:
21
33
  impl = ToolFn.FnImpl(s=fn, a=lang.as_async(fn, wrap=True))
22
34
 
35
+ #
36
+
37
+ tf_input: ToolFn.Input
23
38
  sig = inspect.signature(fn)
24
- if sig.return_annotation is not str:
25
- raise NotImplementedError(fn)
39
+ if marshal_input:
40
+ in_rtys: dict[str, rfl.Type] = {}
41
+ for p in sig.parameters.values():
42
+ p_rty = rfl.type_(p.annotation)
43
+ if not no_marshal_check:
44
+ msh.global_marshaling().new_unmarshal_context().make(p_rty)
45
+ in_rtys[p.name] = p_rty
46
+ tf_input = ToolFn.MarshalInput(in_rtys)
47
+ else:
48
+ tf_input = ToolFn.RawKwargsInput()
49
+
50
+ #
51
+
52
+ tf_output: ToolFn.Output
53
+ if marshal_output:
54
+ out_rty = rfl.type_(sig.return_annotation)
55
+ if not no_marshal_check:
56
+ msh.global_marshaling().new_marshal_context().make(out_rty)
57
+ tf_output = ToolFn.MarshalOutput(out_rty)
58
+ else:
59
+ if sig.return_annotation is not str:
60
+ raise NotImplementedError(fn)
61
+ tf_output = ToolFn.RawStringOutput()
62
+
63
+ #
26
64
 
27
65
  return ToolCatalogEntry(
28
66
  reflect_tool_spec(fn),
29
67
  ToolFn(
30
68
  impl,
31
- ToolFn.KwargsInput(),
32
- ToolFn.RawStringOutput(),
69
+ tf_input,
70
+ tf_output,
33
71
  ),
34
72
  )
@@ -7,6 +7,11 @@ import typing as ta
7
7
  from omlish import check
8
8
  from omlish import dataclasses as dc
9
9
  from omlish import lang
10
+ from omlish import marshal as msh
11
+ from omlish import reflect as rfl
12
+
13
+ from ..content.json import JsonContent
14
+ from ..content.types import Content
10
15
 
11
16
 
12
17
  D = ta.TypeVar('D')
@@ -51,7 +56,11 @@ class ToolFn(lang.Final):
51
56
  check.arg(dc.is_dataclass(self.cls))
52
57
 
53
58
  @dc.dataclass(frozen=True)
54
- class KwargsInput(Input, lang.Final):
59
+ class MarshalInput(Input, lang.Final):
60
+ rtys: ta.Mapping[str, rfl.Type]
61
+
62
+ @dc.dataclass(frozen=True)
63
+ class RawKwargsInput(Input, lang.Final):
55
64
  pass
56
65
 
57
66
  input: Input
@@ -70,6 +79,10 @@ class ToolFn(lang.Final):
70
79
  check.isinstance(self.cls, type)
71
80
  check.arg(dc.is_dataclass(self.cls))
72
81
 
82
+ @dc.dataclass(frozen=True)
83
+ class MarshalOutput(Output, lang.Final):
84
+ rty: rfl.Type
85
+
73
86
  @dc.dataclass(frozen=True)
74
87
  class RawStringOutput(Output, lang.Final):
75
88
  pass
@@ -100,7 +113,7 @@ async def execute_tool_fn(
100
113
  args: ta.Mapping[str, ta.Any],
101
114
  *,
102
115
  forbid_sync_as_async: bool = False,
103
- ) -> str:
116
+ ) -> Content:
104
117
  m_fn: ta.Callable[..., ta.Awaitable[ta.Any]]
105
118
  if isinstance(tfn.impl, ToolFn.FnImpl):
106
119
  s_fn = tfn.impl.s
@@ -115,20 +128,44 @@ async def execute_tool_fn(
115
128
  else:
116
129
  raise TypeError(tfn.impl)
117
130
 
118
- out: ta.Any
131
+ #
132
+
133
+ fn_kw: ta.Mapping[str, ta.Any]
119
134
  if isinstance(tfn.input, ToolFn.DataclassInput):
120
135
  raise NotImplementedError
121
- elif isinstance(tfn.input, ToolFn.KwargsInput):
122
- out = await m_fn(**args)
136
+
137
+ elif isinstance(tfn.input, ToolFn.MarshalInput):
138
+ fn_kw_dct: dict[str, ta.Any] = {}
139
+ for k, v in args.items():
140
+ fn_kw_dct[k] = msh.unmarshal(v, tfn.input.rtys[k])
141
+ fn_kw = fn_kw_dct
142
+
143
+ elif isinstance(tfn.input, ToolFn.RawKwargsInput):
144
+ fn_kw = args
145
+
123
146
  else:
124
- raise NotImplementedError
147
+ raise TypeError(tfn.input)
125
148
 
126
- ret: str
149
+ #
150
+
151
+ fn_out = await m_fn(**fn_kw)
152
+
153
+ #
154
+
155
+ ret: Content
127
156
  if isinstance(tfn.output, ToolFn.DataclassOutput):
128
157
  raise NotImplementedError
158
+
159
+ elif isinstance(tfn.output, ToolFn.MarshalOutput):
160
+ out_v = msh.marshal(fn_out, tfn.output.rty)
161
+ ret = JsonContent(out_v)
162
+
129
163
  elif isinstance(tfn.output, ToolFn.RawStringOutput):
130
- ret = check.isinstance(out, str)
164
+ ret = check.isinstance(fn_out, str)
165
+
131
166
  else:
132
- raise NotImplementedError
167
+ raise TypeError(tfn.output)
168
+
169
+ #
133
170
 
134
171
  return ret
@@ -155,10 +155,10 @@ class ToolReflector:
155
155
 
156
156
  #
157
157
 
158
- def _prepare_desc(self, s: str | None) -> str | None:
158
+ def _prepare_desc(self, s: CanContent) -> CanContent:
159
159
  if s is None:
160
160
  return None
161
- if not self._raw_descs:
161
+ if not self._raw_descs and isinstance(s, str):
162
162
  s = s.strip()
163
163
  return s
164
164
 
@@ -49,7 +49,7 @@ class _VectorUnmarshalerFactory(msh.UnmarshalerFactoryMatchClass):
49
49
 
50
50
 
51
51
  @lang.static_init
52
- def _install_standard_marshalling() -> None:
52
+ def _install_standard_marshaling() -> None:
53
53
  msh.install_standard_factories(
54
54
  _VectorMarshalerFactory(),
55
55
  _VectorUnmarshalerFactory(),
ommlds/tools/ocr.py CHANGED
@@ -34,7 +34,7 @@ else:
34
34
  Ocr: ta.TypeAlias = ta.Callable[['Image.Image'], str]
35
35
 
36
36
  OCR_BACKENDS: ta.Mapping[str, Ocr] = {
37
- 'rapidocr': lambda img: '\n'.join(text[1] for text in rapidocr.RapidOCR()(img)[0] or []),
37
+ 'rapidocr': lambda img: '\n'.join(text[1] for text in rapidocr.RapidOCR()(_get_img_png_bytes(img))[0] or []),
38
38
  'tesseract': lambda img: pytesseract.image_to_string(img),
39
39
  }
40
40
 
@@ -62,6 +62,12 @@ def _get_img_data(file: str | None) -> ta.Any:
62
62
  return sys.stdin.buffer
63
63
 
64
64
 
65
+ def _get_img_png_bytes(img: 'Image.Image') -> bytes:
66
+ out = io.BytesIO()
67
+ img.save(out, format='PNG')
68
+ return out.getvalue()
69
+
70
+
65
71
  def _main() -> None:
66
72
  parser = argparse.ArgumentParser()
67
73
  parser.add_argument('file', nargs='?')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ommlds
3
- Version: 0.0.0.dev450
3
+ Version: 0.0.0.dev451
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.dev450
18
- Requires-Dist: omlish==0.0.0.dev450
17
+ Requires-Dist: omdev==0.0.0.dev451
18
+ Requires-Dist: omlish==0.0.0.dev451
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"