kash-shell 0.3.11__py3-none-any.whl → 0.3.12__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.
Files changed (62) hide show
  1. kash/actions/core/render_as_html.py +2 -2
  2. kash/actions/core/show_webpage.py +2 -2
  3. kash/actions/core/strip_html.py +2 -2
  4. kash/commands/base/basic_file_commands.py +21 -3
  5. kash/commands/base/files_command.py +5 -4
  6. kash/commands/extras/parse_uv_lock.py +12 -3
  7. kash/commands/workspace/selection_commands.py +1 -1
  8. kash/commands/workspace/workspace_commands.py +1 -1
  9. kash/config/env_settings.py +2 -42
  10. kash/config/logger.py +30 -25
  11. kash/config/logger_basic.py +6 -6
  12. kash/config/settings.py +23 -7
  13. kash/config/setup.py +33 -5
  14. kash/config/text_styles.py +25 -22
  15. kash/embeddings/cosine.py +12 -4
  16. kash/embeddings/embeddings.py +16 -6
  17. kash/embeddings/text_similarity.py +10 -4
  18. kash/exec/__init__.py +3 -0
  19. kash/exec/action_decorators.py +4 -19
  20. kash/exec/action_exec.py +43 -23
  21. kash/exec/llm_transforms.py +2 -2
  22. kash/exec/preconditions.py +4 -12
  23. kash/exec/runtime_settings.py +134 -0
  24. kash/exec/shell_callable_action.py +5 -3
  25. kash/file_storage/file_store.py +18 -21
  26. kash/file_storage/item_file_format.py +6 -3
  27. kash/file_storage/store_filenames.py +6 -3
  28. kash/llm_utils/init_litellm.py +16 -0
  29. kash/llm_utils/llm_api_keys.py +6 -2
  30. kash/llm_utils/llm_completion.py +11 -4
  31. kash/mcp/mcp_cli.py +3 -2
  32. kash/mcp/mcp_server_routes.py +11 -12
  33. kash/media_base/transcription_deepgram.py +15 -2
  34. kash/model/__init__.py +1 -1
  35. kash/model/actions_model.py +6 -54
  36. kash/model/exec_model.py +79 -0
  37. kash/model/items_model.py +71 -50
  38. kash/model/operations_model.py +38 -15
  39. kash/model/paths_model.py +2 -0
  40. kash/shell/output/shell_output.py +10 -8
  41. kash/shell/shell_main.py +2 -2
  42. kash/shell/utils/exception_printing.py +2 -2
  43. kash/text_handling/doc_normalization.py +16 -8
  44. kash/text_handling/markdown_utils.py +83 -2
  45. kash/utils/common/format_utils.py +2 -8
  46. kash/utils/common/inflection.py +22 -0
  47. kash/utils/common/task_stack.py +4 -15
  48. kash/utils/errors.py +14 -9
  49. kash/utils/file_utils/file_formats_model.py +15 -0
  50. kash/utils/file_utils/file_sort_filter.py +10 -3
  51. kash/web_gen/templates/base_styles.css.jinja +8 -3
  52. kash/workspaces/__init__.py +12 -3
  53. kash/workspaces/workspace_dirs.py +58 -0
  54. kash/workspaces/workspace_importing.py +1 -1
  55. kash/workspaces/workspaces.py +26 -90
  56. {kash_shell-0.3.11.dist-info → kash_shell-0.3.12.dist-info}/METADATA +4 -4
  57. {kash_shell-0.3.11.dist-info → kash_shell-0.3.12.dist-info}/RECORD +60 -57
  58. kash/shell/utils/argparse_utils.py +0 -20
  59. kash/utils/lang_utils/inflection.py +0 -18
  60. {kash_shell-0.3.11.dist-info → kash_shell-0.3.12.dist-info}/WHEEL +0 -0
  61. {kash_shell-0.3.11.dist-info → kash_shell-0.3.12.dist-info}/entry_points.txt +0 -0
  62. {kash_shell-0.3.11.dist-info → kash_shell-0.3.12.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,134 @@
1
+ import contextvars
2
+ from dataclasses import dataclass
3
+ from pathlib import Path
4
+
5
+ from kash.config.logger import get_logger
6
+ from kash.model.exec_model import RuntimeSettings
7
+ from kash.model.items_model import State
8
+ from kash.workspaces.workspace_dirs import enclosing_ws_dir, global_ws_dir
9
+
10
+ log = get_logger(__name__)
11
+
12
+ _current_settings: contextvars.ContextVar[RuntimeSettings | None] = contextvars.ContextVar(
13
+ "current_runtime_settings", default=None
14
+ )
15
+ """
16
+ Context variable that tracks the current runtime settings. Only used if it is
17
+ explicitly set with a `with runtime_settings(...):` block.
18
+ """
19
+
20
+
21
+ def current_runtime_settings() -> RuntimeSettings:
22
+ """
23
+ Get the current runtime settings. Uses the ambient context var settings if
24
+ set and otherwise infers the workspace from the current working directory
25
+ with default runtime settings.
26
+ """
27
+
28
+ ambient_settings = _current_settings.get()
29
+ if ambient_settings:
30
+ return ambient_settings
31
+
32
+ default_ws_dir = enclosing_ws_dir() or global_ws_dir()
33
+ return RuntimeSettings(default_ws_dir)
34
+
35
+
36
+ @dataclass(frozen=True)
37
+ class WsContext:
38
+ global_ws_dir: Path
39
+ enclosing_ws_dir: Path | None
40
+ override_dir: Path | None
41
+
42
+ @property
43
+ def current_ws_dir(self) -> Path:
44
+ if self.override_dir:
45
+ return self.override_dir
46
+ elif self.enclosing_ws_dir:
47
+ return self.enclosing_ws_dir
48
+ else:
49
+ return self.global_ws_dir
50
+
51
+
52
+ def current_ws_context() -> WsContext:
53
+ """
54
+ Context path info about the current workspace, including the global workspace
55
+ directory, any workspace directory that encloses the current working directory,
56
+ and override set via runtime settings.
57
+ """
58
+
59
+ override_dir = None
60
+ ambient_settings = _current_settings.get()
61
+ if ambient_settings:
62
+ override_dir = ambient_settings.workspace_dir
63
+
64
+ return WsContext(global_ws_dir(), enclosing_ws_dir(), override_dir)
65
+
66
+
67
+ @dataclass
68
+ class RuntimeSettingsManager:
69
+ """
70
+ Manage the context for executing actions, including `RuntimeSettings` and
71
+ `Workspace`.
72
+
73
+ This is a minimal base class for use as a context manager. Most functionality
74
+ is still in `FileStore`.
75
+
76
+ Workspaces may be detected based on the current working directory or explicitly
77
+ set using a `with` block:
78
+ ```
79
+ ws = get_ws("my_workspace")
80
+ with ws:
81
+ # code that calls current_ws() will use this workspace
82
+ ```
83
+ """
84
+
85
+ settings: RuntimeSettings
86
+
87
+ def __enter__(self):
88
+ self._token = _current_settings.set(self.settings)
89
+ log.info("New runtime context: %s", self.settings)
90
+ return self.settings
91
+
92
+ def __exit__(self, exc_type, exc_val, exc_tb):
93
+ _current_settings.reset(self._token)
94
+ log.info("Exiting runtime context: %s", self.settings)
95
+
96
+
97
+ def kash_runtime(
98
+ workspace_dir: Path | None,
99
+ *,
100
+ rerun: bool = False,
101
+ refetch: bool = False,
102
+ override_state: State | None = None,
103
+ tmp_output: bool = False,
104
+ no_format: bool = False,
105
+ ) -> RuntimeSettingsManager:
106
+ """
107
+ Set a specific kash execution context for a with block.
108
+ This allows defining a workspace and other execution settings as the ambient
109
+ context within the block.
110
+
111
+ If `workspace_dir` is not provided, the current workspace will be inferred
112
+ from the working directory or fall back to the global workspace.
113
+
114
+ Example usage:
115
+ ```
116
+ with kash_runtime(ws_path, rerun=args.rerun) as runtime:
117
+ runtime.workspace.log_workspace_info()
118
+ # Perform actions.
119
+ ```
120
+ """
121
+ from kash.workspaces.workspaces import current_ws
122
+
123
+ if workspace_dir is None:
124
+ workspace_dir = current_ws().base_dir
125
+
126
+ settings = RuntimeSettings(
127
+ workspace_dir=workspace_dir,
128
+ rerun=rerun,
129
+ refetch=refetch,
130
+ override_state=override_state,
131
+ tmp_output=tmp_output,
132
+ no_format=no_format,
133
+ )
134
+ return RuntimeSettingsManager(settings=settings)
@@ -13,7 +13,7 @@ from kash.model.params_model import RawParamValues
13
13
  from kash.shell.output.shell_output import PrintHooks
14
14
  from kash.shell.utils.exception_printing import summarize_traceback
15
15
  from kash.utils.common.parse_shell_args import parse_shell_args
16
- from kash.utils.errors import NONFATAL_EXCEPTIONS
16
+ from kash.utils.errors import get_nonfatal_exceptions
17
17
 
18
18
  log = get_logger(__name__)
19
19
 
@@ -71,14 +71,16 @@ class ShellCallableAction:
71
71
  action_cls, explicit_values, *shell_args.args, rerun=rerun, refetch=refetch
72
72
  )
73
73
  # We don't return the result to keep the xonsh shell output clean.
74
- except NONFATAL_EXCEPTIONS as e:
74
+ except get_nonfatal_exceptions() as e:
75
75
  PrintHooks.nonfatal_exception()
76
76
  log.error(f"[{COLOR_ERROR}]Action error:[/{COLOR_ERROR}] %s", summarize_traceback(e))
77
77
  log.info("Action error details: %s", e, exc_info=True)
78
78
  return ShellResult(exception=e)
79
79
  except Exception as e:
80
80
  # Log here while we are in the true call stack (not inside the xonsh call stack).
81
- log.error("Action error: %s", e, exc_info=KashEnv.KASH_SHOW_TRACEBACK.read_bool(True))
81
+ log.error(
82
+ "Action error: %s", e, exc_info=KashEnv.KASH_SHOW_TRACEBACK.read_bool(default=True)
83
+ )
82
84
  raise
83
85
  finally:
84
86
  log_tallies(level="warning", if_slower_than=10.0)
@@ -5,7 +5,7 @@ import time
5
5
  from collections.abc import Callable, Generator
6
6
  from os.path import join, relpath
7
7
  from pathlib import Path
8
- from typing import Any, TypeVar
8
+ from typing import Concatenate, ParamSpec, TypeVar
9
9
 
10
10
  from funlog import format_duration, log_calls
11
11
  from prettyfmt import fmt_lines, fmt_path
@@ -13,13 +13,13 @@ from strif import copyfile_atomic, hash_file, move_file
13
13
  from typing_extensions import override
14
14
 
15
15
  from kash.config.logger import get_log_settings, get_logger
16
- from kash.config.text_styles import EMOJI_SAVED, STYLE_HINT
16
+ from kash.config.text_styles import EMOJI_SAVED
17
17
  from kash.file_storage.item_file_format import read_item, write_item
18
18
  from kash.file_storage.metadata_dirs import MetadataDirs
19
19
  from kash.file_storage.store_filenames import folder_for_type, join_suffix, parse_item_filename
20
20
  from kash.model.items_model import Item, ItemId, ItemType
21
21
  from kash.model.paths_model import StorePath
22
- from kash.shell.output.shell_output import PrintHooks, cprint
22
+ from kash.shell.output.shell_output import PrintHooks
23
23
  from kash.utils.common.format_utils import fmt_loc
24
24
  from kash.utils.common.uniquifier import Uniquifier
25
25
  from kash.utils.common.url import Locator, Url, is_url
@@ -34,16 +34,18 @@ from kash.workspaces.workspaces import Workspace
34
34
  log = get_logger(__name__)
35
35
 
36
36
 
37
+ SelfT = TypeVar("SelfT")
37
38
  T = TypeVar("T")
39
+ P = ParamSpec("P")
38
40
 
39
41
 
40
- def synchronized(method: Callable[..., T]) -> Callable[..., T]:
42
+ def synchronized(method: Callable[Concatenate[SelfT, P], T]) -> Callable[Concatenate[SelfT, P], T]:
41
43
  """
42
44
  Simple way to synchronize a few methods.
43
45
  """
44
46
 
45
47
  @functools.wraps(method)
46
- def synchronized_method(self, *args: Any, **kwargs: Any) -> T:
48
+ def synchronized_method(self, *args: P.args, **kwargs: P.kwargs) -> T:
47
49
  with self._lock:
48
50
  return method(self, *args, **kwargs)
49
51
 
@@ -316,9 +318,11 @@ class FileStore(Workspace):
316
318
  Save the item. Uses the `store_path` if it's already set or generates a new one.
317
319
  Updates `item.store_path`.
318
320
 
321
+ Unless `no_format` is true, also normalizes body text formatting (for Markdown)
322
+ and updates the item's body to match.
323
+
319
324
  If `as_tmp` is true, will save the item to a temporary file.
320
325
  If `overwrite` is false, will skip saving if the item already exists.
321
- If `no_format` is true, will not normalize body text formatting (for Markdown).
322
326
  """
323
327
  # If external file already exists within the workspace, the file is already saved (without metadata).
324
328
  external_path = item.external_path and Path(item.external_path).resolve()
@@ -475,7 +479,6 @@ class FileStore(Workspace):
475
479
  store_path = self.save(item)
476
480
  log.info("Imported text file: %s", item.as_str())
477
481
  else:
478
- log.message("Importing non-text file: %s", fmt_loc(path))
479
482
  # Binary or other files we just copy over as-is, preserving the name.
480
483
  # We know the extension is recognized.
481
484
  store_path, _found, old_store_path = self.store_path_for(item)
@@ -586,12 +589,13 @@ class FileStore(Workspace):
586
589
  move_file(full_input_path, original_path)
587
590
  return StorePath(store_path)
588
591
 
589
- def log_workspace_info(self, *, once: bool = False):
592
+ @synchronized
593
+ def log_workspace_info(self, *, once: bool = False) -> bool:
590
594
  """
591
595
  Log helpful information about the workspace.
592
596
  """
593
597
  if once and self.info_logged:
594
- return
598
+ return False
595
599
 
596
600
  self.info_logged = True
597
601
 
@@ -606,25 +610,18 @@ class FileStore(Workspace):
606
610
  fmt_path(get_log_settings().log_file_path.absolute(), rel_to_cwd=False),
607
611
  )
608
612
  log.message(
609
- "Media cache: %s", fmt_path(self.base_dir / self.dirs.media_cache_dir, rel_to_cwd=False)
610
- )
611
- log.message(
612
- "Content cache: %s",
613
+ "Caches: %s, %s",
614
+ fmt_path(self.base_dir / self.dirs.media_cache_dir, rel_to_cwd=False),
613
615
  fmt_path(self.base_dir / self.dirs.content_cache_dir, rel_to_cwd=False),
614
616
  )
617
+ log.message("Current working directory: %s", fmt_path(Path.cwd(), rel_to_cwd=False))
618
+
615
619
  for warning in self.warnings:
616
620
  log.warning("%s", warning)
617
621
 
618
- if self.is_global_ws:
619
- PrintHooks.spacer()
620
- log.warning("Note you are currently using the default global workspace.")
621
- cprint(
622
- "Create or switch to another workspace with the `workspace` command.",
623
- style=STYLE_HINT,
624
- )
625
-
626
622
  log.info("File store startup took %s.", format_duration(self.end_time - self.start_time))
627
623
  # TODO: Log more info like number of items by type.
624
+ return True
628
625
 
629
626
  def walk_items(
630
627
  self,
@@ -7,7 +7,7 @@ from prettyfmt import custom_key_sort, fmt_size_human
7
7
  from kash.config.logger import get_logger
8
8
  from kash.model.items_model import ITEM_FIELDS, Item
9
9
  from kash.model.operations_model import OPERATION_FIELDS
10
- from kash.text_handling.doc_normalization import normalize_formatting_ansi
10
+ from kash.text_handling.doc_normalization import normalize_formatting
11
11
  from kash.utils.common.format_utils import fmt_loc
12
12
  from kash.utils.file_utils.file_formats_model import Format
13
13
  from kash.utils.file_utils.mtime_cache import MtimeCache
@@ -25,7 +25,7 @@ _item_cache = MtimeCache[Item](max_size=2000, name="Item")
25
25
  def write_item(item: Item, path: Path, normalize: bool = True):
26
26
  """
27
27
  Write a text item to a file with standard frontmatter format YAML.
28
- Also normalizes formatting of the body text.
28
+ By default normalizes formatting of the body text and updates the item's body.
29
29
  """
30
30
  item.validate()
31
31
  if item.is_binary:
@@ -37,7 +37,7 @@ def write_item(item: Item, path: Path, normalize: bool = True):
37
37
  _item_cache.delete(path)
38
38
 
39
39
  if normalize:
40
- body = normalize_formatting_ansi(item.body_text(), item.format)
40
+ body = normalize_formatting(item.body_text(), item.format)
41
41
  else:
42
42
  body = item.body_text()
43
43
 
@@ -79,6 +79,9 @@ def write_item(item: Item, path: Path, normalize: bool = True):
79
79
  # Update cache.
80
80
  _item_cache.update(path, item)
81
81
 
82
+ # Update the item's body to reflect normalization.
83
+ item.body = body
84
+
82
85
 
83
86
  def read_item(path: Path, base_dir: Path | None) -> Item:
84
87
  """
@@ -1,15 +1,18 @@
1
+ from functools import cache
1
2
  from pathlib import Path
2
3
 
3
4
  from kash.config.logger import get_logger
4
5
  from kash.model.items_model import ItemType
6
+ from kash.utils.common.inflection import plural
5
7
  from kash.utils.file_utils.file_formats_model import FileExt, Format
6
8
  from kash.utils.file_utils.filename_parsing import split_filename
7
- from kash.utils.lang_utils.inflection import plural
8
9
 
9
10
  log = get_logger(__name__)
10
11
 
11
12
 
12
- _type_to_folder = {name: plural(name) for name, _value in ItemType.__members__.items()}
13
+ @cache
14
+ def _get_type_to_folder() -> dict[str, str]:
15
+ return {name: plural(name) for name, _value in ItemType.__members__.items()}
13
16
 
14
17
 
15
18
  def folder_for_type(item_type: ItemType) -> Path:
@@ -22,7 +25,7 @@ def folder_for_type(item_type: ItemType) -> Path:
22
25
  export -> exports
23
26
  etc.
24
27
  """
25
- return Path(_type_to_folder[item_type.name])
28
+ return Path(_get_type_to_folder()[item_type.name])
26
29
 
27
30
 
28
31
  def join_suffix(base_slug: str, full_suffix: str) -> str:
@@ -0,0 +1,16 @@
1
+ from functools import cache
2
+
3
+
4
+ @cache
5
+ def init_litellm():
6
+ """
7
+ Configure litellm to suppress overly prominent exception messages.
8
+ Do this lazily since litellm is slow to import.
9
+ """
10
+ try:
11
+ import litellm
12
+ from litellm import _logging # noqa: F401
13
+
14
+ litellm.suppress_debug_info = True # Suppress overly prominent exception messages.
15
+ except ImportError:
16
+ pass
@@ -1,10 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- import litellm
4
3
  from clideps.env_vars.dotenv_utils import env_var_is_set
5
4
  from clideps.env_vars.env_names import EnvName
6
- from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
7
5
 
6
+ from kash.llm_utils.init_litellm import init_litellm
8
7
  from kash.llm_utils.llm_names import LLMName
9
8
  from kash.llm_utils.llms import LLM
10
9
 
@@ -13,6 +12,11 @@ def api_for_model(model: LLMName) -> EnvName | None:
13
12
  """
14
13
  Get the API key name for a model or None if not found.
15
14
  """
15
+ import litellm
16
+ from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
17
+
18
+ init_litellm()
19
+
16
20
  try:
17
21
  _model, custom_llm_provider, _dynamic_api_key, _api_base = get_llm_provider(model)
18
22
  except litellm.exceptions.BadRequestError:
@@ -1,11 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  import time
2
- from typing import cast
4
+ from typing import TYPE_CHECKING, cast
3
5
 
4
- import litellm
5
6
  from flowmark import Wrap, fill_text
6
7
  from funlog import format_duration, log_calls
7
- from litellm.types.utils import Choices, ModelResponse
8
- from litellm.types.utils import Message as LiteLLMMessage
9
8
  from prettyfmt import slugify_snake
10
9
  from pydantic import BaseModel
11
10
  from pydantic.dataclasses import dataclass
@@ -13,12 +12,16 @@ from pydantic.dataclasses import dataclass
13
12
  from kash.config.logger import get_logger
14
13
  from kash.config.text_styles import EMOJI_TIMING
15
14
  from kash.llm_utils.fuzzy_parsing import is_no_results
15
+ from kash.llm_utils.init_litellm import init_litellm
16
16
  from kash.llm_utils.llm_messages import Message, MessageTemplate
17
17
  from kash.llm_utils.llm_names import LLMName
18
18
  from kash.utils.common.url import Url, is_url
19
19
  from kash.utils.errors import ApiResultError
20
20
  from kash.utils.file_formats.chat_format import ChatHistory, ChatMessage, ChatRole
21
21
 
22
+ if TYPE_CHECKING:
23
+ from litellm.types.utils import Message as LiteLLMMessage
24
+
22
25
  log = get_logger(__name__)
23
26
 
24
27
 
@@ -68,6 +71,10 @@ def llm_completion(
68
71
  """
69
72
  Perform an LLM completion with LiteLLM.
70
73
  """
74
+ import litellm
75
+ from litellm.types.utils import Choices, ModelResponse
76
+
77
+ init_litellm()
71
78
 
72
79
  chat_history = ChatHistory.from_dicts(messages)
73
80
  log.info(
kash/mcp/mcp_cli.py CHANGED
@@ -9,9 +9,10 @@ import logging
9
9
  import os
10
10
  from pathlib import Path
11
11
 
12
+ from clideps.utils.readable_argparse import ReadableColorFormatter
13
+
12
14
  from kash.config.settings import DEFAULT_MCP_SERVER_PORT, LogLevel, global_settings
13
15
  from kash.config.setup import kash_setup
14
- from kash.shell.utils.argparse_utils import WrappedColorFormatter
15
16
  from kash.shell.version import get_version
16
17
 
17
18
  __version__ = get_version()
@@ -27,7 +28,7 @@ log = logging.getLogger()
27
28
  def build_parser():
28
29
  from kash.workspaces.workspaces import global_ws_dir
29
30
 
30
- parser = argparse.ArgumentParser(description=__doc__, formatter_class=WrappedColorFormatter)
31
+ parser = argparse.ArgumentParser(description=__doc__, formatter_class=ReadableColorFormatter)
31
32
  parser.add_argument(
32
33
  "--version",
33
34
  action="version",
@@ -13,12 +13,13 @@ from strif import AtomicVar
13
13
  from kash.config.capture_output import CapturedOutput, captured_output
14
14
  from kash.config.logger import get_logger
15
15
  from kash.config.settings import global_settings
16
+ from kash.exec import kash_runtime
16
17
  from kash.exec.action_exec import prepare_action_input, run_action_with_caching
17
18
  from kash.exec.action_registry import get_all_actions_defaults, look_up_action_class
18
- from kash.model.actions_model import Action, ActionResult, ExecContext
19
+ from kash.model.actions_model import Action, ActionResult
20
+ from kash.model.exec_model import ExecContext
19
21
  from kash.model.params_model import TypedParamValues
20
22
  from kash.model.paths_model import StorePath
21
- from kash.workspaces.workspaces import current_ws, get_ws
22
23
 
23
24
  log = get_logger(__name__)
24
25
 
@@ -214,9 +215,14 @@ def run_mcp_tool(action_name: str, arguments: dict) -> list[TextContent]:
214
215
  # current workspace, which could be changed by the user by changing working
215
216
  # directories. Maybe confusing?
216
217
  explicit_mcp_ws = global_settings().mcp_ws_dir
217
- ws = get_ws(explicit_mcp_ws) if explicit_mcp_ws else current_ws()
218
218
 
219
- with ws:
219
+ with kash_runtime(
220
+ workspace_dir=explicit_mcp_ws,
221
+ rerun=True, # Enabling rerun always for now, seems good for tools.
222
+ refetch=False, # Using the file caches.
223
+ # Keeping all transient files for now, but maybe make transient?
224
+ override_state=None,
225
+ ) as exec_settings:
220
226
  action_cls = look_up_action_class(action_name)
221
227
 
222
228
  # Extract items array and remaining params from arguments.
@@ -228,14 +234,7 @@ def run_mcp_tool(action_name: str, arguments: dict) -> list[TextContent]:
228
234
  action = action_cls.create(param_values)
229
235
 
230
236
  # Create execution context and assemble action input.
231
- context = ExecContext(
232
- action=action,
233
- workspace_dir=ws.base_dir,
234
- rerun=True, # Enabling rerun always for now, seems good for tools.
235
- refetch=False, # Using the file caches.
236
- # Keeping all transient files for now, but maybe make transient?
237
- override_state=None,
238
- )
237
+ context = ExecContext(action=action, settings=exec_settings)
239
238
  action_input = prepare_action_input(*input_items)
240
239
 
241
240
  result, result_store_paths, _archived_store_paths = run_action_with_caching(
@@ -1,8 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  from os.path import getsize
2
4
  from pathlib import Path
5
+ from typing import TYPE_CHECKING
3
6
 
4
7
  from clideps.env_vars.dotenv_utils import load_dotenv_paths
5
- from deepgram import ListenRESTClient, PrerecordedResponse
6
8
  from httpx import Timeout
7
9
 
8
10
  from kash.config.logger import CustomLogger, get_logger
@@ -10,6 +12,9 @@ from kash.config.settings import global_settings
10
12
  from kash.media_base.transcription_format import SpeakerSegment, format_speaker_segments
11
13
  from kash.utils.errors import ApiError, ContentError
12
14
 
15
+ if TYPE_CHECKING:
16
+ from deepgram import PrerecordedResponse
17
+
13
18
  log: CustomLogger = get_logger(__name__)
14
19
 
15
20
 
@@ -19,7 +24,15 @@ def deepgram_transcribe_raw(
19
24
  """
20
25
  Transcribe an audio file using Deepgram and return the raw response.
21
26
  """
22
- from deepgram import ClientOptionsFromEnv, DeepgramClient, FileSource, PrerecordedOptions
27
+ # Slow import, do lazily.
28
+ from deepgram import (
29
+ ClientOptionsFromEnv,
30
+ DeepgramClient,
31
+ FileSource,
32
+ ListenRESTClient,
33
+ PrerecordedOptions,
34
+ PrerecordedResponse,
35
+ )
23
36
 
24
37
  size = getsize(audio_file_path)
25
38
  log.info(
kash/model/__init__.py CHANGED
@@ -20,7 +20,6 @@ from kash.model.actions_model import (
20
20
  Action,
21
21
  ActionInput,
22
22
  ActionResult,
23
- ExecContext,
24
23
  LLMOptions,
25
24
  PathOp,
26
25
  PathOpType,
@@ -33,6 +32,7 @@ from kash.model.compound_actions_model import (
33
32
  look_up_actions,
34
33
  )
35
34
  from kash.model.concept_model import Concept, canonicalize_concept, normalize_concepts
35
+ from kash.model.exec_model import ExecContext
36
36
  from kash.model.graph_model import GraphData, Link, Node
37
37
  from kash.model.items_model import (
38
38
  SLUG_MAX_LEN,
@@ -4,7 +4,6 @@ from abc import ABC, abstractmethod
4
4
  from dataclasses import Field as DataclassField
5
5
  from dataclasses import field, replace
6
6
  from enum import Enum
7
- from pathlib import Path
8
7
  from textwrap import dedent
9
8
  from typing import Any, TypeVar, cast
10
9
 
@@ -20,10 +19,10 @@ from typing_extensions import override
20
19
  from kash.config.logger import get_logger
21
20
  from kash.exec_model.args_model import NO_ARGS, ONE_ARG, ArgCount, ArgType, Signature
22
21
  from kash.exec_model.shell_model import ShellResult
23
- from kash.file_storage.file_store import FileStore
24
22
  from kash.llm_utils import LLM, LLMName
25
23
  from kash.llm_utils.llm_messages import Message, MessageTemplate
26
- from kash.model.items_model import UNTITLED, Item, ItemType, State
24
+ from kash.model.exec_model import ExecContext
25
+ from kash.model.items_model import UNTITLED, Item, ItemType
27
26
  from kash.model.operations_model import Operation, Source
28
27
  from kash.model.params_model import (
29
28
  ALL_COMMON_PARAMS,
@@ -38,7 +37,6 @@ from kash.model.preconditions_model import Precondition
38
37
  from kash.utils.common.parse_key_vals import format_key_value
39
38
  from kash.utils.common.type_utils import not_none
40
39
  from kash.utils.errors import InvalidDefinition, InvalidInput
41
- from kash.workspaces.workspaces import get_ws
42
40
 
43
41
  log = get_logger(__name__)
44
42
 
@@ -64,53 +62,6 @@ class ActionInput:
64
62
  return ActionInput(items=[])
65
63
 
66
64
 
67
- @dataclass(frozen=True)
68
- class ExecContext:
69
- """
70
- An action and its context for execution. This is a good place for settings
71
- that apply to any action and are bothersome to pass as parameters.
72
- """
73
-
74
- action: Action
75
- """The action being executed."""
76
-
77
- workspace_dir: Path
78
- """The workspace directory in which the action is being executed."""
79
-
80
- rerun: bool = False
81
- """If True, always run actions, even cacheable ones that have results."""
82
-
83
- refetch: bool = False
84
- """If True, will refetch items even if they are already in the content caches."""
85
-
86
- override_state: State | None = None
87
- """If specified, override the state of result items. Useful to mark items as transient."""
88
-
89
- tmp_output: bool = False
90
- """If True, will save output items to a temporary file."""
91
-
92
- no_format: bool = False
93
- """If True, will not normalize the output item's body text formatting (for Markdown)."""
94
-
95
- @property
96
- def workspace(self) -> FileStore:
97
- return get_ws(self.workspace_dir)
98
-
99
- @property
100
- def runtime_options(self) -> dict[str, str]:
101
- """Return non-default runtime options."""
102
- opts: dict[str, str] = {}
103
- # Only these two settings directly affect the output:
104
- if self.no_format:
105
- opts["no_format"] = "true"
106
- if self.override_state:
107
- opts["override_state"] = self.override_state.name
108
- return opts
109
-
110
- def __repr__(self):
111
- return abbrev_obj(self, field_max_len=80)
112
-
113
-
114
65
  class PathOpType(Enum):
115
66
  archive = "archive"
116
67
  select = "select"
@@ -365,8 +316,8 @@ class Action(ABC):
365
316
  """
366
317
  Declaration sanity checks.
367
318
  """
368
- if not self.name or not self.description:
369
- raise InvalidDefinition("Action must have a name and description")
319
+ if not self.name:
320
+ raise InvalidDefinition("Action must have a name")
370
321
 
371
322
  for param in self.params:
372
323
  if not self.has_param(param.name):
@@ -535,7 +486,7 @@ class Action(ABC):
535
486
  log.info("Ignoring parameter for action `%s`: `%s`", self.name, param_name)
536
487
 
537
488
  if overrides:
538
- log.message(
489
+ log.info(
539
490
  "Overriding parameters for action `%s`:\n%s",
540
491
  self.name,
541
492
  fmt_lines(overrides),
@@ -677,3 +628,4 @@ class PerItemAction(Action, ABC):
677
628
 
678
629
  # Handle circular dependency in Python dataclasses.
679
630
  rebuild_dataclass(Item) # pyright: ignore
631
+ rebuild_dataclass(ExecContext) # pyright: ignore