kash-shell 0.3.10__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 (83) hide show
  1. kash/actions/core/format_markdown_template.py +2 -5
  2. kash/actions/core/markdownify.py +2 -4
  3. kash/actions/core/readability.py +2 -4
  4. kash/actions/core/render_as_html.py +30 -11
  5. kash/actions/core/show_webpage.py +6 -11
  6. kash/actions/core/strip_html.py +4 -8
  7. kash/actions/core/{webpage_config.py → tabbed_webpage_config.py} +5 -3
  8. kash/actions/core/{webpage_generate.py → tabbed_webpage_generate.py} +5 -4
  9. kash/commands/base/basic_file_commands.py +21 -3
  10. kash/commands/base/files_command.py +29 -10
  11. kash/commands/extras/parse_uv_lock.py +12 -3
  12. kash/commands/workspace/selection_commands.py +1 -1
  13. kash/commands/workspace/workspace_commands.py +2 -3
  14. kash/config/colors.py +2 -2
  15. kash/config/env_settings.py +2 -42
  16. kash/config/logger.py +30 -25
  17. kash/config/logger_basic.py +6 -6
  18. kash/config/settings.py +23 -7
  19. kash/config/setup.py +33 -5
  20. kash/config/text_styles.py +25 -22
  21. kash/embeddings/cosine.py +12 -4
  22. kash/embeddings/embeddings.py +16 -6
  23. kash/embeddings/text_similarity.py +10 -4
  24. kash/exec/__init__.py +3 -0
  25. kash/exec/action_decorators.py +10 -25
  26. kash/exec/action_exec.py +43 -23
  27. kash/exec/llm_transforms.py +6 -3
  28. kash/exec/preconditions.py +10 -12
  29. kash/exec/resolve_args.py +4 -0
  30. kash/exec/runtime_settings.py +134 -0
  31. kash/exec/shell_callable_action.py +5 -3
  32. kash/file_storage/file_store.py +37 -38
  33. kash/file_storage/item_file_format.py +6 -3
  34. kash/file_storage/store_filenames.py +6 -3
  35. kash/help/function_param_info.py +1 -1
  36. kash/llm_utils/init_litellm.py +16 -0
  37. kash/llm_utils/llm_api_keys.py +6 -2
  38. kash/llm_utils/llm_completion.py +11 -4
  39. kash/local_server/local_server_routes.py +1 -7
  40. kash/mcp/mcp_cli.py +3 -2
  41. kash/mcp/mcp_server_routes.py +11 -12
  42. kash/media_base/transcription_deepgram.py +15 -2
  43. kash/model/__init__.py +1 -1
  44. kash/model/actions_model.py +6 -54
  45. kash/model/exec_model.py +79 -0
  46. kash/model/items_model.py +102 -35
  47. kash/model/operations_model.py +38 -15
  48. kash/model/paths_model.py +2 -0
  49. kash/shell/output/shell_output.py +10 -8
  50. kash/shell/shell_main.py +2 -2
  51. kash/shell/utils/exception_printing.py +2 -2
  52. kash/shell/utils/shell_function_wrapper.py +15 -15
  53. kash/text_handling/doc_normalization.py +16 -8
  54. kash/text_handling/markdown_render.py +1 -0
  55. kash/text_handling/markdown_utils.py +105 -2
  56. kash/utils/common/format_utils.py +2 -8
  57. kash/utils/common/function_inspect.py +360 -110
  58. kash/utils/common/inflection.py +22 -0
  59. kash/utils/common/task_stack.py +4 -15
  60. kash/utils/errors.py +14 -9
  61. kash/utils/file_utils/file_ext.py +4 -0
  62. kash/utils/file_utils/file_formats_model.py +32 -1
  63. kash/utils/file_utils/file_sort_filter.py +10 -3
  64. kash/web_gen/__init__.py +0 -4
  65. kash/web_gen/simple_webpage.py +52 -0
  66. kash/web_gen/tabbed_webpage.py +23 -16
  67. kash/web_gen/template_render.py +37 -2
  68. kash/web_gen/templates/base_styles.css.jinja +84 -59
  69. kash/web_gen/templates/base_webpage.html.jinja +85 -67
  70. kash/web_gen/templates/item_view.html.jinja +47 -37
  71. kash/web_gen/templates/simple_webpage.html.jinja +24 -0
  72. kash/web_gen/templates/tabbed_webpage.html.jinja +42 -32
  73. kash/workspaces/__init__.py +12 -3
  74. kash/workspaces/workspace_dirs.py +58 -0
  75. kash/workspaces/workspace_importing.py +1 -1
  76. kash/workspaces/workspaces.py +26 -90
  77. {kash_shell-0.3.10.dist-info → kash_shell-0.3.12.dist-info}/METADATA +7 -7
  78. {kash_shell-0.3.10.dist-info → kash_shell-0.3.12.dist-info}/RECORD +81 -76
  79. kash/shell/utils/argparse_utils.py +0 -20
  80. kash/utils/lang_utils/inflection.py +0 -18
  81. {kash_shell-0.3.10.dist-info → kash_shell-0.3.12.dist-info}/WHEEL +0 -0
  82. {kash_shell-0.3.10.dist-info → kash_shell-0.3.12.dist-info}/entry_points.txt +0 -0
  83. {kash_shell-0.3.10.dist-info → kash_shell-0.3.12.dist-info}/licenses/LICENSE +0 -0
@@ -3,7 +3,7 @@ import sys
3
3
  from logging import FileHandler, Formatter, LogRecord
4
4
  from pathlib import Path
5
5
 
6
- from kash.config.settings import LogLevel
6
+ from kash.config.settings import LogLevel, LogLevelStr
7
7
  from kash.config.suppress_warnings import demote_warnings
8
8
 
9
9
  # Basic logging setup for non-interactive logging, like on a server.
@@ -16,21 +16,21 @@ class SuppressedWarningsStreamHandler(logging.StreamHandler):
16
16
  super().emit(record)
17
17
 
18
18
 
19
- def basic_file_handler(path: Path, level: LogLevel) -> logging.FileHandler:
19
+ def basic_file_handler(path: Path, level: LogLevel | LogLevelStr) -> logging.FileHandler:
20
20
  handler = logging.FileHandler(path)
21
- handler.setLevel(level.value)
21
+ handler.setLevel(LogLevel.parse(level).value)
22
22
  handler.setFormatter(Formatter("%(asctime)s %(levelname).1s %(name)s - %(message)s"))
23
23
  return handler
24
24
 
25
25
 
26
- def basic_stderr_handler(level: LogLevel) -> logging.StreamHandler:
26
+ def basic_stderr_handler(level: LogLevel | LogLevelStr) -> logging.StreamHandler:
27
27
  handler = SuppressedWarningsStreamHandler(stream=sys.stderr)
28
- handler.setLevel(level.value)
28
+ handler.setLevel(LogLevel.parse(level).value)
29
29
  handler.setFormatter(Formatter("%(asctime)s %(levelname).1s %(name)s - %(message)s"))
30
30
  return handler
31
31
 
32
32
 
33
- def basic_logging_setup(log_path: Path | None, level: LogLevel):
33
+ def basic_logging_setup(log_path: Path | None, level: LogLevel | LogLevelStr):
34
34
  """
35
35
  Set up basic logging to a file and to stderr.
36
36
  """
kash/config/settings.py CHANGED
@@ -1,10 +1,13 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
- from enum import Enum
4
+ from dataclasses import dataclass
5
+ from enum import IntEnum
3
6
  from functools import cache
4
7
  from logging import DEBUG, ERROR, INFO, WARNING
5
8
  from pathlib import Path
9
+ from typing import Literal
6
10
 
7
- from pydantic.dataclasses import dataclass
8
11
  from strif import AtomicVar
9
12
 
10
13
  from kash.config.env_settings import KashEnv
@@ -64,7 +67,14 @@ LOCAL_SERVER_PORTS_MAX = 30
64
67
  LOCAL_SERVER_LOG_NAME = "local_server"
65
68
 
66
69
 
67
- class LogLevel(Enum):
70
+ LogLevelStr = Literal["debug", "info", "message", "warning", "error"]
71
+
72
+
73
+ class LogLevel(IntEnum):
74
+ """
75
+ Convenience enum for log levels with parsing and ordering.
76
+ """
77
+
68
78
  debug = DEBUG
69
79
  info = INFO
70
80
  warning = WARNING
@@ -72,7 +82,9 @@ class LogLevel(Enum):
72
82
  error = ERROR
73
83
 
74
84
  @classmethod
75
- def parse(cls, level_str: str):
85
+ def parse(cls, level_str: str | LogLevelStr | LogLevel) -> LogLevel:
86
+ if isinstance(level_str, LogLevel):
87
+ return level_str
76
88
  canon_name = level_str.strip().lower()
77
89
  if canon_name == "warn":
78
90
  canon_name = "warning"
@@ -87,7 +99,7 @@ class LogLevel(Enum):
87
99
  return self.name
88
100
 
89
101
 
90
- DEFAULT_LOG_LEVEL = LogLevel.parse(KashEnv.KASH_LOG_LEVEL.read_str("warning"))
102
+ DEFAULT_LOG_LEVEL = LogLevel.parse(KashEnv.KASH_LOG_LEVEL.read_str(default="warning"))
91
103
 
92
104
 
93
105
  def resolve_and_create_dirs(path: Path | str, is_dir: bool = False) -> Path:
@@ -174,6 +186,9 @@ class Settings:
174
186
  console_log_level: LogLevel
175
187
  """The log level for console-based logging."""
176
188
 
189
+ console_quiet: bool
190
+ """If true, suppress non-logging console output."""
191
+
177
192
  file_log_level: LogLevel
178
193
  """The log level for file-based logging."""
179
194
 
@@ -205,7 +220,7 @@ def _get_ws_root_dir() -> Path:
205
220
 
206
221
 
207
222
  def _get_global_ws_dir() -> Path:
208
- kash_ws_dir = KashEnv.KASH_GLOBAL_WS.read_path()
223
+ kash_ws_dir = KashEnv.KASH_GLOBAL_WS.read_path(default=None)
209
224
  if kash_ws_dir:
210
225
  return kash_ws_dir
211
226
  else:
@@ -225,7 +240,7 @@ def _get_system_cache_dir() -> Path:
225
240
 
226
241
 
227
242
  def _get_mcp_ws_dir() -> Path | None:
228
- mcp_dir = KashEnv.KASH_MCP_WS.read_str()
243
+ mcp_dir = KashEnv.KASH_MCP_WS.read_str(default=None)
229
244
  if mcp_dir:
230
245
  return Path(mcp_dir).expanduser().resolve()
231
246
  else:
@@ -254,6 +269,7 @@ def _read_settings():
254
269
  default_editor="nano",
255
270
  file_log_level=LogLevel.info,
256
271
  console_log_level=DEFAULT_LOG_LEVEL,
272
+ console_quiet=False,
257
273
  local_server_ports_start=LOCAL_SERVER_PORT_START,
258
274
  local_server_ports_max=LOCAL_SERVER_PORTS_MAX,
259
275
  local_server_port=0,
kash/config/setup.py CHANGED
@@ -7,7 +7,13 @@ from clideps.env_vars.dotenv_utils import load_dotenv_paths
7
7
 
8
8
  from kash.config.logger import reset_rich_logging
9
9
  from kash.config.logger_basic import basic_logging_setup
10
- from kash.config.settings import LogLevel, configure_ws_and_settings, global_settings
10
+ from kash.config.settings import (
11
+ LogLevel,
12
+ LogLevelStr,
13
+ atomic_global_settings,
14
+ configure_ws_and_settings,
15
+ global_settings,
16
+ )
11
17
 
12
18
 
13
19
  @cache
@@ -16,7 +22,9 @@ def kash_setup(
16
22
  rich_logging: bool,
17
23
  kash_ws_root: Path | None = None,
18
24
  log_path: Path | None = None,
19
- level: LogLevel = LogLevel.info,
25
+ log_level: LogLevel | LogLevelStr | None = None,
26
+ console_log_level: LogLevel | LogLevelStr | None = None,
27
+ console_quiet: bool | None = None,
20
28
  ):
21
29
  """
22
30
  One-time top-level setup of essential logging, keys, directories, and configs.
@@ -25,8 +33,14 @@ def kash_setup(
25
33
  Can call this if embedding kash in another app.
26
34
  Can be used to set the global default workspace and logs directory
27
35
  and/or the default log file.
28
- If `rich_logging` is True, then rich logging with warnings only for console use.
29
- If `rich_logging` is False, then use basic logging to a file and stderr.
36
+
37
+ Basic logging is to the specified log file.
38
+ If enabled, rich logging is to the console as well.
39
+
40
+ By default console is "warning" level but can be controlled with
41
+ the `console_log_level` parameter.
42
+ All console/shell output can be suppressed with `console_quiet`. By default
43
+ console is quiet if `console_log_level` is "error" or higher.
30
44
  """
31
45
  from kash.utils.common.stack_traces import add_stacktrace_handler
32
46
 
@@ -40,10 +54,24 @@ def kash_setup(
40
54
  configure_ws_and_settings(kash_ws_root)
41
55
 
42
56
  # Now set up logging, as it might depend on workspace root.
57
+ log_level = LogLevel.parse(log_level) if log_level else LogLevel.info
58
+
43
59
  if rich_logging:
60
+ # These settings are only used for rich logging.
61
+ console_log_level = (
62
+ LogLevel.parse(console_log_level) if console_log_level else LogLevel.warning
63
+ )
64
+ console_quiet = (
65
+ console_quiet if console_quiet is not None else console_log_level >= LogLevel.error
66
+ )
67
+
68
+ with atomic_global_settings().updates() as settings:
69
+ settings.console_log_level = console_log_level
70
+ settings.file_log_level = log_level
71
+ settings.console_quiet = console_quiet
44
72
  reset_rich_logging(log_path=log_path)
45
73
  else:
46
- basic_logging_setup(log_path=log_path, level=level)
74
+ basic_logging_setup(log_path=log_path, level=log_level)
47
75
 
48
76
  _lib_setup()
49
77
 
@@ -73,8 +73,8 @@ COLOR_VALUE = "cyan"
73
73
  COLOR_PATH = "cyan"
74
74
  COLOR_HINT = "bright_black"
75
75
  COLOR_HINT_DIM = "dim bright_black"
76
- COLOR_SKIP = "green"
77
- COLOR_TASK = "magenta"
76
+ COLOR_SKIP = "bright_blue"
77
+ COLOR_ACTION = "magenta"
78
78
  COLOR_SAVED = "blue"
79
79
  COLOR_TIMING = "bright_black"
80
80
  COLOR_CALL = "bright_yellow"
@@ -133,7 +133,7 @@ RICH_STYLES = {
133
133
  "hint": COLOR_HINT,
134
134
  "hint_dim": COLOR_HINT_DIM,
135
135
  "skip": COLOR_SKIP,
136
- "task": COLOR_TASK,
136
+ "action": COLOR_ACTION,
137
137
  "saved": COLOR_SAVED,
138
138
  "timing": COLOR_TIMING,
139
139
  "call": COLOR_CALL,
@@ -218,12 +218,14 @@ RICH_STYLES = {
218
218
  "kash.size_k": STYLE_SIZE2,
219
219
  "kash.size_m": STYLE_SIZE3,
220
220
  "kash.size_gtp": STYLE_SIZE4,
221
- "kash.filename": Style(color=COLOR_VALUE),
222
- "kash.task_stack_header": Style(color=COLOR_TASK),
223
- "kash.task_stack": Style(color=COLOR_TASK),
221
+ "kash.filename": Style(color=COLOR_HINT),
222
+ "kash.start_action": Style(color=COLOR_ACTION, bold=True),
223
+ "kash.task_stack_header": Style(color=COLOR_HINT),
224
+ "kash.task_stack": Style(color=COLOR_HINT),
224
225
  "kash.task_stack_prefix": Style(color=COLOR_HINT),
225
226
  # Emoji colors:
226
- "kash.task": Style(color=COLOR_TASK),
227
+ "kash.action": Style(color=COLOR_ACTION),
228
+ "kash.start": Style(color=COLOR_ACTION, bold=True),
227
229
  "kash.success": Style(color=COLOR_SUCCESS, bold=True),
228
230
  "kash.skip": Style(color=COLOR_SKIP, bold=True),
229
231
  "kash.failure": Style(color=COLOR_ERROR, bold=True),
@@ -259,17 +261,24 @@ PROMPT_ASSIST = "(assistant) ❯"
259
261
 
260
262
  EMOJI_HINT = "👉"
261
263
 
262
- # More ideas: ⦿⧁⧀⦿⦾⟐⦊⟡
264
+ EMOJI_MSG_INDENT = "⋮"
263
265
 
266
+ EMOJI_START = "[➤]"
264
267
 
265
- EMOJI_COMMAND = ""
268
+ EMOJI_SUCCESS = "[✔︎]"
266
269
 
267
- EMOJI_ACTION = ""
270
+ EMOJI_SKIP = "[-]"
271
+
272
+ EMOJI_FAILURE = "[✘]"
268
273
 
269
274
  EMOJI_SNIPPET = "❯"
270
275
 
271
276
  EMOJI_HELP = "?"
272
277
 
278
+ EMOJI_ACTION = "⛭"
279
+
280
+ EMOJI_COMMAND = "⧁" # More ideas: ⦿⧁⧀⦿⦾⟐⦊⟡
281
+
273
282
  EMOJI_SHELL = "⦊"
274
283
 
275
284
  EMOJI_RECOMMENDED = "•"
@@ -282,26 +291,18 @@ EMOJI_SAVED = "⩣"
282
291
 
283
292
  EMOJI_TIMING = "⏱"
284
293
 
285
- EMOJI_SUCCESS = "[✔︎]"
286
-
287
- EMOJI_SKIP = "[∕]"
288
-
289
- EMOJI_FAILURE = "[✘]"
290
-
291
294
  EMOJI_CALL_BEGIN = "≫"
292
295
 
293
296
  EMOJI_CALL_END = "≪"
294
297
 
295
298
  EMOJI_ASSISTANT = "🤖"
296
299
 
297
- EMOJI_MSG_INDENT = "⋮"
298
-
299
300
  EMOJI_BREADCRUMB_SEP = "›"
300
301
 
301
302
 
302
303
  ## Special headings
303
304
 
304
- TASK_STACK_HEADER = "Task stack:"
305
+ TASK_STACK_HEADER = "Task stack"
305
306
 
306
307
 
307
308
  ## Rich setup
@@ -320,15 +321,17 @@ class KashHighlighter(RegexHighlighter):
320
321
  base_style = "kash."
321
322
  highlights = [
322
323
  _combine_regex(
324
+ # Important patterns that color the whole line:
325
+ f"(?P<start_action>{re.escape(EMOJI_START + ' Action')}.*)",
326
+ f"(?P<timing>{re.escape(EMOJI_TIMING)}.*)",
323
327
  # Task stack in logs:
324
328
  f"(?P<task_stack_header>{re.escape(TASK_STACK_HEADER)})",
325
329
  f"(?P<task_stack>{re.escape(EMOJI_BREADCRUMB_SEP)}.*)",
326
330
  f"(?P<task_stack_prefix>{re.escape(EMOJI_MSG_INDENT)})",
327
- # Emojis that color the whole line:
328
- f"(?P<timing>{re.escape(EMOJI_TIMING)}.*)",
329
331
  # Color emojis by themselves:
330
332
  f"(?P<saved>{re.escape(EMOJI_SAVED)})",
331
- f"(?P<task>{re.escape(EMOJI_ACTION)})",
333
+ f"(?P<action>{re.escape(EMOJI_ACTION)})",
334
+ f"(?P<start>{re.escape(EMOJI_START)})",
332
335
  f"(?P<success>{re.escape(EMOJI_SUCCESS)})",
333
336
  f"(?P<skip>{re.escape(EMOJI_SKIP)})",
334
337
  f"(?P<failure>{re.escape(EMOJI_FAILURE)})",
kash/embeddings/cosine.py CHANGED
@@ -1,10 +1,16 @@
1
+ from __future__ import annotations
2
+
1
3
  from collections.abc import Sequence
2
- from typing import Any, TypeAlias
4
+ from typing import TYPE_CHECKING, Any, TypeAlias
3
5
 
4
- import numpy as np
6
+ if TYPE_CHECKING:
7
+ from numpy import ndarray
5
8
 
6
- # Type aliases for clarity
7
- ArrayLike: TypeAlias = Sequence[float] | np.ndarray[Any, Any]
9
+ # Type aliases for clarity
10
+ ArrayLike: TypeAlias = Sequence[float] | ndarray[Any, Any]
11
+ else:
12
+ # Keep numpy import lazy.
13
+ ArrayLike = Any
8
14
 
9
15
 
10
16
  def cosine(u: ArrayLike, v: ArrayLike) -> float:
@@ -12,6 +18,8 @@ def cosine(u: ArrayLike, v: ArrayLike) -> float:
12
18
  Compute the cosine distance between two 1-D arrays.
13
19
  Could use scipy.spatial.distance.cosine, but avoiding the dependency.
14
20
  """
21
+ import numpy as np
22
+
15
23
  # Convert to numpy arrays
16
24
  u_array = np.asarray(u, dtype=np.float64)
17
25
  v_array = np.asarray(v, dtype=np.float64)
@@ -3,17 +3,18 @@ from __future__ import annotations
3
3
  import ast
4
4
  from collections.abc import Iterable
5
5
  from pathlib import Path
6
- from typing import TypeAlias, cast
6
+ from typing import TYPE_CHECKING, TypeAlias, cast
7
7
 
8
- import pandas as pd
9
- from litellm import embedding
10
- from litellm.types.utils import EmbeddingResponse
11
8
  from pydantic.dataclasses import dataclass
12
9
  from strif import abbrev_list
13
10
 
14
11
  from kash.config.logger import get_logger
12
+ from kash.llm_utils.init_litellm import init_litellm
15
13
  from kash.llm_utils.llms import DEFAULT_EMBEDDING_MODEL
16
14
 
15
+ if TYPE_CHECKING:
16
+ from pandas import DataFrame
17
+
17
18
  log = get_logger(__name__)
18
19
 
19
20
 
@@ -41,11 +42,13 @@ class Embeddings:
41
42
  def as_iterable(self) -> Iterable[tuple[Key, str, list[float]]]:
42
43
  return ((key, text, emb) for key, (text, emb) in self.data.items())
43
44
 
44
- def as_df(self) -> pd.DataFrame:
45
+ def as_df(self) -> DataFrame:
46
+ from pandas import DataFrame
47
+
45
48
  keys, texts, embeddings = zip(
46
49
  *[(key, text, emb) for key, (text, emb) in self.data.items()], strict=False
47
50
  )
48
- return pd.DataFrame(
51
+ return DataFrame(
49
52
  {
50
53
  "key": keys,
51
54
  "text": texts,
@@ -61,6 +64,11 @@ class Embeddings:
61
64
 
62
65
  @classmethod
63
66
  def embed(cls, keyvals: list[KeyVal], model=DEFAULT_EMBEDDING_MODEL) -> Embeddings:
67
+ from litellm import embedding
68
+ from litellm.types.utils import EmbeddingResponse
69
+
70
+ init_litellm()
71
+
64
72
  data = {}
65
73
  log.message(
66
74
  "Embedding %d texts (model %s, batch size %s)…",
@@ -102,6 +110,8 @@ class Embeddings:
102
110
 
103
111
  @classmethod
104
112
  def read_from_csv(cls, path: Path) -> Embeddings:
113
+ import pandas as pd
114
+
105
115
  df = pd.read_csv(path)
106
116
  df["embedding"] = df["embedding"].apply(ast.literal_eval)
107
117
  data = {row["key"]: (row["text"], row["embedding"]) for _, row in df.iterrows()}
@@ -1,9 +1,8 @@
1
- from typing import cast
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, cast
2
4
 
3
- import litellm
4
5
  from funlog import log_calls
5
- from litellm import embedding
6
- from litellm.types.utils import EmbeddingResponse
7
6
 
8
7
  from kash.config.logger import get_logger
9
8
  from kash.embeddings.cosine import ArrayLike, cosine
@@ -11,6 +10,9 @@ from kash.embeddings.embeddings import Embeddings
11
10
  from kash.llm_utils.llms import DEFAULT_EMBEDDING_MODEL, EmbeddingModel
12
11
  from kash.utils.errors import ApiResultError
13
12
 
13
+ if TYPE_CHECKING:
14
+ from litellm.types.utils import EmbeddingResponse
15
+
14
16
  log = get_logger(__name__)
15
17
 
16
18
 
@@ -20,6 +22,10 @@ def cosine_relatedness(x: ArrayLike, y: ArrayLike) -> float:
20
22
 
21
23
  @log_calls(level="info", show_return_value=False)
22
24
  def embed_query(model: EmbeddingModel, query: str) -> EmbeddingResponse:
25
+ import litellm
26
+ from litellm import embedding
27
+ from litellm.types.utils import EmbeddingResponse
28
+
23
29
  try:
24
30
  response: EmbeddingResponse = cast(
25
31
  EmbeddingResponse, embedding(model=model.litellm_name, input=[query])
kash/exec/__init__.py CHANGED
@@ -12,6 +12,7 @@ from kash.exec.resolve_args import (
12
12
  resolve_locator_arg,
13
13
  resolve_path_arg,
14
14
  )
15
+ from kash.exec.runtime_settings import current_runtime_settings, kash_runtime
15
16
 
16
17
  __all__ = [
17
18
  "kash_action",
@@ -20,6 +21,8 @@ __all__ = [
20
21
  "prepare_action_input",
21
22
  "run_action_with_shell_context",
22
23
  "kash_command",
24
+ "kash_runtime",
25
+ "current_runtime_settings",
23
26
  "import_and_register",
24
27
  "llm_transform_item",
25
28
  "llm_transform_str",
@@ -21,22 +21,22 @@ from typing_extensions import override
21
21
  from kash.config.logger import get_logger
22
22
  from kash.exec.action_exec import run_action_with_caching
23
23
  from kash.exec.action_registry import register_action_class
24
+ from kash.exec.runtime_settings import current_runtime_settings
24
25
  from kash.exec_model.args_model import ONE_ARG, ArgCount, ArgType
25
26
  from kash.model.actions_model import (
26
27
  Action,
27
28
  ActionInput,
28
29
  ActionResult,
29
- ExecContext,
30
30
  LLMOptions,
31
31
  ParamSource,
32
32
  TitleTemplate,
33
33
  )
34
- from kash.model.items_model import Item, ItemType, State
34
+ from kash.model.exec_model import ExecContext
35
+ from kash.model.items_model import Item, ItemType
35
36
  from kash.model.params_model import Param, ParamDeclarations, TypedParamValues
36
37
  from kash.model.preconditions_model import Precondition
37
38
  from kash.utils.common.function_inspect import FuncParam, inspect_function_params
38
39
  from kash.utils.errors import InvalidDefinition
39
- from kash.workspaces.workspaces import current_ws
40
40
 
41
41
  log = get_logger(__name__)
42
42
 
@@ -174,7 +174,7 @@ def _merge_param_declarations(
174
174
  merged_params[fp.name] = Param(
175
175
  name=fp.name,
176
176
  description=None,
177
- type=fp.type or str,
177
+ type=fp.effective_type or str,
178
178
  default_value=fp.default if fp.has_default else None,
179
179
  is_explicit=not fp.has_default,
180
180
  )
@@ -209,13 +209,6 @@ def kash_action(
209
209
  mcp_tool: bool = False,
210
210
  title_template: TitleTemplate = TitleTemplate("{title}"),
211
211
  llm_options: LLMOptions = LLMOptions(),
212
- override_state: State | None = None,
213
- # Including these for completeness but usually don't want to set them globally
214
- # in the decorator:
215
- rerun: bool = False,
216
- refetch: bool = False,
217
- tmp_output: bool = False,
218
- no_format: bool = False,
219
212
  ) -> Callable[[AF], AF]:
220
213
  """
221
214
  A function decorator to create and register an action. The annotated function must
@@ -248,13 +241,13 @@ def kash_action(
248
241
 
249
242
  # Inspect and sanity check the formal params.
250
243
  func_params = inspect_function_params(orig_func)
251
- if len(func_params) == 0 or func_params[0].type not in (ActionInput, Item):
244
+ if len(func_params) == 0 or func_params[0].effective_type not in (ActionInput, Item):
252
245
  raise InvalidDefinition(
253
246
  f"Decorator `@kash_action` requires exactly one positional parameter, "
254
247
  f"`input` of type `ActionInput` or `Item` on function `{orig_func.__name__}` but "
255
248
  f"got params: {func_params}"
256
249
  )
257
- if any(fp.is_positional for fp in func_params[1:]):
250
+ if any(fp.is_pure_positional for fp in func_params[1:]):
258
251
  raise InvalidDefinition(
259
252
  "Decorator `@kash_action` requires all parameters after the first positional "
260
253
  f"parameter to be keyword parameters on function `{orig_func.__name__}` but "
@@ -265,7 +258,7 @@ def kash_action(
265
258
  context_param = next((fp for fp in func_params if fp.name == "context"), None)
266
259
  if context_param:
267
260
  func_params.remove(context_param)
268
- if context_param and context_param.is_positional:
261
+ if context_param and context_param.is_pure_positional:
269
262
  raise InvalidDefinition(
270
263
  "Decorator `@kash_action` requires the `context` parameter to be a keyword "
271
264
  "parameter, not positional, on function `{func.__name__}`"
@@ -273,7 +266,7 @@ def kash_action(
273
266
 
274
267
  # If the original function is a simple action function (processes a single item),
275
268
  # wrap it to convert to an ActionFunction.
276
- is_simple_func = func_params[0].type == Item
269
+ is_simple_func = func_params[0].effective_type == Item
277
270
  action_func: ActionFunction
278
271
  if is_simple_func:
279
272
  simple_func = cast(SimpleActionFunction, orig_func)
@@ -333,7 +326,7 @@ def kash_action(
333
326
  if context_param:
334
327
  kw_args["context"] = context
335
328
  for fp in func_params[1:]:
336
- if fp.is_positional:
329
+ if fp.is_pure_positional:
337
330
  pos_args.append(self.get_param(fp.name))
338
331
  else:
339
332
  kw_args[fp.name] = self.get_param(fp.name)
@@ -386,15 +379,7 @@ def kash_action(
386
379
  if provided_context:
387
380
  context = provided_context
388
381
  else:
389
- context = ExecContext(
390
- action,
391
- current_ws().base_dir,
392
- rerun=rerun,
393
- refetch=refetch,
394
- override_state=override_state,
395
- tmp_output=tmp_output,
396
- no_format=no_format,
397
- )
382
+ context = ExecContext(action, current_runtime_settings())
398
383
 
399
384
  # Run the action.
400
385
  result, _, _ = run_action_with_caching(context, action_input)