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.
- kash/actions/core/render_as_html.py +2 -2
- kash/actions/core/show_webpage.py +2 -2
- kash/actions/core/strip_html.py +2 -2
- kash/commands/base/basic_file_commands.py +21 -3
- kash/commands/base/files_command.py +5 -4
- kash/commands/extras/parse_uv_lock.py +12 -3
- kash/commands/workspace/selection_commands.py +1 -1
- kash/commands/workspace/workspace_commands.py +1 -1
- kash/config/env_settings.py +2 -42
- kash/config/logger.py +30 -25
- kash/config/logger_basic.py +6 -6
- kash/config/settings.py +23 -7
- kash/config/setup.py +33 -5
- kash/config/text_styles.py +25 -22
- kash/embeddings/cosine.py +12 -4
- kash/embeddings/embeddings.py +16 -6
- kash/embeddings/text_similarity.py +10 -4
- kash/exec/__init__.py +3 -0
- kash/exec/action_decorators.py +4 -19
- kash/exec/action_exec.py +43 -23
- kash/exec/llm_transforms.py +2 -2
- kash/exec/preconditions.py +4 -12
- kash/exec/runtime_settings.py +134 -0
- kash/exec/shell_callable_action.py +5 -3
- kash/file_storage/file_store.py +18 -21
- kash/file_storage/item_file_format.py +6 -3
- kash/file_storage/store_filenames.py +6 -3
- kash/llm_utils/init_litellm.py +16 -0
- kash/llm_utils/llm_api_keys.py +6 -2
- kash/llm_utils/llm_completion.py +11 -4
- kash/mcp/mcp_cli.py +3 -2
- kash/mcp/mcp_server_routes.py +11 -12
- kash/media_base/transcription_deepgram.py +15 -2
- kash/model/__init__.py +1 -1
- kash/model/actions_model.py +6 -54
- kash/model/exec_model.py +79 -0
- kash/model/items_model.py +71 -50
- kash/model/operations_model.py +38 -15
- kash/model/paths_model.py +2 -0
- kash/shell/output/shell_output.py +10 -8
- kash/shell/shell_main.py +2 -2
- kash/shell/utils/exception_printing.py +2 -2
- kash/text_handling/doc_normalization.py +16 -8
- kash/text_handling/markdown_utils.py +83 -2
- kash/utils/common/format_utils.py +2 -8
- kash/utils/common/inflection.py +22 -0
- kash/utils/common/task_stack.py +4 -15
- kash/utils/errors.py +14 -9
- kash/utils/file_utils/file_formats_model.py +15 -0
- kash/utils/file_utils/file_sort_filter.py +10 -3
- kash/web_gen/templates/base_styles.css.jinja +8 -3
- kash/workspaces/__init__.py +12 -3
- kash/workspaces/workspace_dirs.py +58 -0
- kash/workspaces/workspace_importing.py +1 -1
- kash/workspaces/workspaces.py +26 -90
- {kash_shell-0.3.11.dist-info → kash_shell-0.3.12.dist-info}/METADATA +4 -4
- {kash_shell-0.3.11.dist-info → kash_shell-0.3.12.dist-info}/RECORD +60 -57
- kash/shell/utils/argparse_utils.py +0 -20
- kash/utils/lang_utils/inflection.py +0 -18
- {kash_shell-0.3.11.dist-info → kash_shell-0.3.12.dist-info}/WHEEL +0 -0
- {kash_shell-0.3.11.dist-info → kash_shell-0.3.12.dist-info}/entry_points.txt +0 -0
- {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
|
|
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
|
|
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(
|
|
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)
|
kash/file_storage/file_store.py
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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[
|
|
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:
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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
|
kash/llm_utils/llm_api_keys.py
CHANGED
|
@@ -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:
|
kash/llm_utils/llm_completion.py
CHANGED
|
@@ -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=
|
|
31
|
+
parser = argparse.ArgumentParser(description=__doc__, formatter_class=ReadableColorFormatter)
|
|
31
32
|
parser.add_argument(
|
|
32
33
|
"--version",
|
|
33
34
|
action="version",
|
kash/mcp/mcp_server_routes.py
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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,
|
kash/model/actions_model.py
CHANGED
|
@@ -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.
|
|
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
|
|
369
|
-
raise InvalidDefinition("Action must have a name
|
|
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.
|
|
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
|