kash-shell 0.3.0__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 (269) hide show
  1. kash/__init__.py +2 -0
  2. kash/__main__.py +4 -0
  3. kash/actions/__init__.py +55 -0
  4. kash/actions/core/assistant_chat.py +45 -0
  5. kash/actions/core/chat.py +90 -0
  6. kash/actions/core/format_markdown_template.py +92 -0
  7. kash/actions/core/markdownify.py +29 -0
  8. kash/actions/core/readability.py +27 -0
  9. kash/actions/core/show_webpage.py +28 -0
  10. kash/actions/core/strip_html.py +28 -0
  11. kash/actions/core/summarize_as_bullets.py +53 -0
  12. kash/actions/core/webpage_config.py +21 -0
  13. kash/actions/core/webpage_generate.py +29 -0
  14. kash/actions/meta/write_instructions.py +39 -0
  15. kash/actions/meta/write_new_action.py +157 -0
  16. kash/commands/__init__.py +21 -0
  17. kash/commands/base/basic_file_commands.py +183 -0
  18. kash/commands/base/browser_commands.py +62 -0
  19. kash/commands/base/debug_commands.py +214 -0
  20. kash/commands/base/diff_commands.py +90 -0
  21. kash/commands/base/files_command.py +408 -0
  22. kash/commands/base/general_commands.py +104 -0
  23. kash/commands/base/global_state_commands.py +41 -0
  24. kash/commands/base/logs_commands.py +92 -0
  25. kash/commands/base/reformat_command.py +54 -0
  26. kash/commands/base/search_command.py +65 -0
  27. kash/commands/base/show_command.py +69 -0
  28. kash/commands/extras/utils_commands.py +27 -0
  29. kash/commands/help/assistant_commands.py +97 -0
  30. kash/commands/help/doc_commands.py +226 -0
  31. kash/commands/help/help_commands.py +133 -0
  32. kash/commands/workspace/selection_commands.py +200 -0
  33. kash/commands/workspace/workspace_commands.py +640 -0
  34. kash/concepts/concept_formats.py +23 -0
  35. kash/concepts/embeddings.py +130 -0
  36. kash/concepts/text_similarity.py +108 -0
  37. kash/config/__init__.py +4 -0
  38. kash/config/api_keys.py +84 -0
  39. kash/config/capture_output.py +77 -0
  40. kash/config/colors.py +279 -0
  41. kash/config/init.py +18 -0
  42. kash/config/lazy_imports.py +22 -0
  43. kash/config/logger.py +355 -0
  44. kash/config/logger_basic.py +35 -0
  45. kash/config/logo.txt +4 -0
  46. kash/config/logo_fancy.txt +4 -0
  47. kash/config/server_config.py +51 -0
  48. kash/config/settings.py +196 -0
  49. kash/config/setup.py +51 -0
  50. kash/config/suppress_warnings.py +27 -0
  51. kash/config/text_styles.py +426 -0
  52. kash/docs/__init__.py +0 -0
  53. kash/docs/all_docs.py +58 -0
  54. kash/docs/load_actions_info.py +28 -0
  55. kash/docs/load_api_docs.py +13 -0
  56. kash/docs/load_help_topics.py +47 -0
  57. kash/docs/load_source_code.py +125 -0
  58. kash/docs/markdown/api_docs_template.md +42 -0
  59. kash/docs/markdown/assistant_instructions_template.md +114 -0
  60. kash/docs/markdown/readme_template.md +26 -0
  61. kash/docs/markdown/topics/a1_what_is_kash.md +76 -0
  62. kash/docs/markdown/topics/a2_progress.md +96 -0
  63. kash/docs/markdown/topics/a3_installation.md +119 -0
  64. kash/docs/markdown/topics/a4_getting_started.md +300 -0
  65. kash/docs/markdown/topics/a5_tips_for_use_with_other_tools.md +83 -0
  66. kash/docs/markdown/topics/b0_philosophy_of_kash.md +177 -0
  67. kash/docs/markdown/topics/b1_kash_overview.md +124 -0
  68. kash/docs/markdown/topics/b2_workspace_and_file_formats.md +61 -0
  69. kash/docs/markdown/topics/b3_modern_shell_tool_recommendations.md +83 -0
  70. kash/docs/markdown/topics/b4_faq.md +166 -0
  71. kash/docs/markdown/warning.md +7 -0
  72. kash/docs/markdown/welcome.md +9 -0
  73. kash/docs_base/docs_base.py +85 -0
  74. kash/docs_base/load_custom_command_info.py +27 -0
  75. kash/docs_base/load_faqs.py +48 -0
  76. kash/docs_base/load_recipe_snippets.py +48 -0
  77. kash/docs_base/recipes/general_system_commands.ksh +10 -0
  78. kash/docs_base/recipes/python_dev_commands.ksh +7 -0
  79. kash/docs_base/recipes/tldr_standard_commands.ksh +2144 -0
  80. kash/errors.py +176 -0
  81. kash/exec/__init__.py +16 -0
  82. kash/exec/action_decorators.py +412 -0
  83. kash/exec/action_exec.py +457 -0
  84. kash/exec/action_registry.py +123 -0
  85. kash/exec/combiners.py +127 -0
  86. kash/exec/command_exec.py +34 -0
  87. kash/exec/command_registry.py +72 -0
  88. kash/exec/fetch_url_metadata.py +71 -0
  89. kash/exec/history.py +44 -0
  90. kash/exec/llm_transforms.py +121 -0
  91. kash/exec/precondition_checks.py +71 -0
  92. kash/exec/precondition_registry.py +43 -0
  93. kash/exec/preconditions.py +152 -0
  94. kash/exec/resolve_args.py +123 -0
  95. kash/exec/shell_callable_action.py +90 -0
  96. kash/exec_model/__init__.py +0 -0
  97. kash/exec_model/args_model.py +93 -0
  98. kash/exec_model/commands_model.py +163 -0
  99. kash/exec_model/script_model.py +161 -0
  100. kash/exec_model/shell_model.py +21 -0
  101. kash/file_storage/__init__.py +0 -0
  102. kash/file_storage/file_store.py +642 -0
  103. kash/file_storage/item_file_format.py +152 -0
  104. kash/file_storage/metadata_dirs.py +108 -0
  105. kash/file_storage/mtime_cache.py +108 -0
  106. kash/file_storage/persisted_yaml.py +37 -0
  107. kash/file_storage/store_cache_warmer.py +37 -0
  108. kash/file_storage/store_filenames.py +53 -0
  109. kash/form_input/__init__.py +0 -0
  110. kash/form_input/prompt_input.py +44 -0
  111. kash/help/__init__.py +0 -0
  112. kash/help/assistant.py +324 -0
  113. kash/help/assistant_instructions.py +68 -0
  114. kash/help/assistant_output.py +43 -0
  115. kash/help/docstring_utils.py +111 -0
  116. kash/help/function_param_info.py +44 -0
  117. kash/help/help_embeddings.py +85 -0
  118. kash/help/help_lookups.py +60 -0
  119. kash/help/help_pages.py +122 -0
  120. kash/help/help_printing.py +169 -0
  121. kash/help/help_types.py +247 -0
  122. kash/help/recommended_commands.py +143 -0
  123. kash/help/tldr_help.py +296 -0
  124. kash/llm_utils/__init__.py +0 -0
  125. kash/llm_utils/chat_format.py +413 -0
  126. kash/llm_utils/clean_headings.py +65 -0
  127. kash/llm_utils/fuzzy_parsing.py +119 -0
  128. kash/llm_utils/language_models.py +178 -0
  129. kash/llm_utils/llm_completion.py +172 -0
  130. kash/llm_utils/llm_messages.py +36 -0
  131. kash/local_server/__init__.py +2 -0
  132. kash/local_server/local_server.py +183 -0
  133. kash/local_server/local_server_commands.py +55 -0
  134. kash/local_server/local_server_routes.py +306 -0
  135. kash/local_server/local_url_formatters.py +169 -0
  136. kash/local_server/port_tools.py +67 -0
  137. kash/local_server/rich_html_template.py +12 -0
  138. kash/mcp/__init__.py +2 -0
  139. kash/mcp/mcp_main.py +67 -0
  140. kash/mcp/mcp_server_commands.py +57 -0
  141. kash/mcp/mcp_server_routes.py +256 -0
  142. kash/mcp/mcp_server_sse.py +143 -0
  143. kash/mcp/mcp_server_stdio.py +45 -0
  144. kash/media_base/__init__.py +0 -0
  145. kash/media_base/audio_processing.py +27 -0
  146. kash/media_base/media_cache.py +178 -0
  147. kash/media_base/media_services.py +112 -0
  148. kash/media_base/media_tools.py +48 -0
  149. kash/media_base/services/local_file_media.py +165 -0
  150. kash/media_base/speech_transcription.py +224 -0
  151. kash/media_base/timestamp_citations.py +80 -0
  152. kash/model/__init__.py +73 -0
  153. kash/model/actions_model.py +633 -0
  154. kash/model/assistant_response_model.py +87 -0
  155. kash/model/compound_actions_model.py +188 -0
  156. kash/model/graph_model.py +92 -0
  157. kash/model/items_model.py +821 -0
  158. kash/model/language_list.py +39 -0
  159. kash/model/llm_actions_model.py +63 -0
  160. kash/model/media_model.py +124 -0
  161. kash/model/operations_model.py +176 -0
  162. kash/model/params_model.py +435 -0
  163. kash/model/paths_model.py +458 -0
  164. kash/model/preconditions_model.py +98 -0
  165. kash/shell/__init__.py +0 -0
  166. kash/shell/completions/completion_scoring.py +280 -0
  167. kash/shell/completions/completion_types.py +154 -0
  168. kash/shell/completions/shell_completions.py +277 -0
  169. kash/shell/file_icons/color_for_format.py +70 -0
  170. kash/shell/file_icons/nerd_icons.py +946 -0
  171. kash/shell/output/__init__.py +0 -0
  172. kash/shell/output/kerm_code_utils.py +59 -0
  173. kash/shell/output/kerm_codes.py +588 -0
  174. kash/shell/output/kmarkdown.py +117 -0
  175. kash/shell/output/shell_output.py +477 -0
  176. kash/shell/ui/__init__.py +0 -0
  177. kash/shell/ui/shell_results.py +118 -0
  178. kash/shell/ui/shell_syntax.py +26 -0
  179. kash/shell/utils/exception_printing.py +50 -0
  180. kash/shell/utils/native_utils.py +240 -0
  181. kash/shell/utils/osc_utils.py +95 -0
  182. kash/shell/utils/shell_function_wrapper.py +204 -0
  183. kash/shell/utils/sys_tool_deps.py +289 -0
  184. kash/shell/utils/terminal_images.py +133 -0
  185. kash/shell_main.py +67 -0
  186. kash/text_handling/custom_sliding_transforms.py +266 -0
  187. kash/text_handling/doc_normalization.py +64 -0
  188. kash/text_handling/markdown_util.py +167 -0
  189. kash/text_handling/unified_diffs.py +138 -0
  190. kash/utils/__init__.py +4 -0
  191. kash/utils/common/__init__.py +4 -0
  192. kash/utils/common/atomic_var.py +147 -0
  193. kash/utils/common/format_utils.py +81 -0
  194. kash/utils/common/function_inspect.py +178 -0
  195. kash/utils/common/import_utils.py +89 -0
  196. kash/utils/common/lazyobject.py +144 -0
  197. kash/utils/common/obj_replace.py +78 -0
  198. kash/utils/common/parse_key_vals.py +85 -0
  199. kash/utils/common/parse_shell_args.py +348 -0
  200. kash/utils/common/stack_traces.py +49 -0
  201. kash/utils/common/string_replace.py +93 -0
  202. kash/utils/common/string_template.py +101 -0
  203. kash/utils/common/task_stack.py +162 -0
  204. kash/utils/common/type_utils.py +137 -0
  205. kash/utils/common/uniquifier.py +95 -0
  206. kash/utils/common/url.py +155 -0
  207. kash/utils/file_utils/__init__.py +3 -0
  208. kash/utils/file_utils/dir_size.py +48 -0
  209. kash/utils/file_utils/file_ext.py +86 -0
  210. kash/utils/file_utils/file_formats.py +134 -0
  211. kash/utils/file_utils/file_formats_model.py +408 -0
  212. kash/utils/file_utils/file_sort_filter.py +235 -0
  213. kash/utils/file_utils/file_walk.py +163 -0
  214. kash/utils/file_utils/filename_parsing.py +99 -0
  215. kash/utils/file_utils/git_tools.py +19 -0
  216. kash/utils/file_utils/ignore_files.py +166 -0
  217. kash/utils/file_utils/path_utils.py +36 -0
  218. kash/utils/lang_utils/__init__.py +0 -0
  219. kash/utils/lang_utils/capitalization.py +128 -0
  220. kash/utils/lang_utils/inflection.py +18 -0
  221. kash/utils/rich_custom/__init__.py +3 -0
  222. kash/utils/rich_custom/ansi_cell_len.py +72 -0
  223. kash/utils/rich_custom/rich_char_transform.py +89 -0
  224. kash/utils/rich_custom/rich_indent.py +69 -0
  225. kash/utils/rich_custom/rich_markdown_fork.py +771 -0
  226. kash/version.py +31 -0
  227. kash/web_content/canon_url.py +24 -0
  228. kash/web_content/dir_store.py +103 -0
  229. kash/web_content/file_cache_utils.py +117 -0
  230. kash/web_content/local_file_cache.py +247 -0
  231. kash/web_content/web_extract.py +55 -0
  232. kash/web_content/web_extract_justext.py +86 -0
  233. kash/web_content/web_extract_readabilipy.py +23 -0
  234. kash/web_content/web_fetch.py +101 -0
  235. kash/web_content/web_page_model.py +28 -0
  236. kash/web_gen/__init__.py +4 -0
  237. kash/web_gen/tabbed_webpage.py +149 -0
  238. kash/web_gen/template_render.py +29 -0
  239. kash/web_gen/templates/base_styles.css.jinja +192 -0
  240. kash/web_gen/templates/base_webpage.html.jinja +124 -0
  241. kash/web_gen/templates/content_styles.css.jinja +194 -0
  242. kash/web_gen/templates/explain_view.html.jinja +49 -0
  243. kash/web_gen/templates/item_view.html.jinja +294 -0
  244. kash/web_gen/templates/tabbed_webpage.html.jinja +49 -0
  245. kash/workspaces/__init__.py +13 -0
  246. kash/workspaces/param_state.py +24 -0
  247. kash/workspaces/selections.py +333 -0
  248. kash/workspaces/source_items.py +88 -0
  249. kash/workspaces/workspace_importing.py +56 -0
  250. kash/workspaces/workspace_names.py +33 -0
  251. kash/workspaces/workspace_output.py +154 -0
  252. kash/workspaces/workspace_registry.py +78 -0
  253. kash/workspaces/workspaces.py +197 -0
  254. kash/xonsh_custom/custom_shell.py +366 -0
  255. kash/xonsh_custom/customize_prompt.py +197 -0
  256. kash/xonsh_custom/customize_xonsh.py +112 -0
  257. kash/xonsh_custom/shell_load_commands.py +152 -0
  258. kash/xonsh_custom/shell_which.py +64 -0
  259. kash/xonsh_custom/xonsh_completers.py +715 -0
  260. kash/xonsh_custom/xonsh_env.py +28 -0
  261. kash/xonsh_custom/xonsh_modern_tools.py +56 -0
  262. kash/xonsh_custom/xonsh_ranking_completer.py +152 -0
  263. kash/xontrib/fnm.py +120 -0
  264. kash/xontrib/kash_extension.py +61 -0
  265. kash_shell-0.3.0.dist-info/METADATA +757 -0
  266. kash_shell-0.3.0.dist-info/RECORD +269 -0
  267. kash_shell-0.3.0.dist-info/WHEEL +4 -0
  268. kash_shell-0.3.0.dist-info/entry_points.txt +3 -0
  269. kash_shell-0.3.0.dist-info/licenses/LICENSE +664 -0
@@ -0,0 +1,435 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+ from dataclasses import field, replace
5
+ from enum import Enum
6
+ from pathlib import Path
7
+ from typing import Any, Generic, TypeAlias, TypeVar
8
+
9
+ from chopdiff.docs import TextUnit
10
+ from prettyfmt import fmt_lines
11
+ from pydantic.dataclasses import dataclass
12
+ from pydantic.json_schema import JsonSchemaValue
13
+
14
+ from kash.config.logger import get_logger
15
+ from kash.errors import InvalidInput, InvalidParamName
16
+ from kash.llm_utils.language_models import LLM, LLMName
17
+ from kash.model.language_list import LANGUAGE_LIST
18
+ from kash.utils.common.parse_key_vals import format_key_value
19
+ from kash.utils.common.type_utils import instantiate_as_type
20
+
21
+ log = get_logger(__name__)
22
+
23
+
24
+ T = TypeVar("T")
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class Param(Generic[T]):
29
+ """
30
+ Describes a settable parameter. This describes the parameter itself (including type and
31
+ default value). It includes the (global) default value for the parameter but not its
32
+ actual value. May be used globally or as an option to a command or action.
33
+ """
34
+
35
+ name: str
36
+
37
+ description: str | None
38
+
39
+ type: type[T]
40
+
41
+ default_value: T | None = None
42
+ """
43
+ The default value for the parameter.
44
+ """
45
+
46
+ is_explicit: bool = False
47
+ """
48
+ Normally a parameter can have a global or action-specific default value. But if this
49
+ is true, the parameter is like an input that is always explicitly required at runtime
50
+ (like a query string).
51
+ """
52
+
53
+ valid_str_values: list[str] | None = None
54
+ """
55
+ If the parameter is a string but has only certain allowed or suggested values,
56
+ list them here. Not necessary for enums, which are handled automatically.
57
+ """
58
+
59
+ is_open_ended: bool = False
60
+ """
61
+ If true and `valid_str_values` is set, the parameter can take any string value and
62
+ the `valid_str_values` are suggestions only.
63
+ """
64
+
65
+ def __post_init__(self):
66
+ if not self.name or not self.name.replace("_", "").isalnum():
67
+ raise ValueError(f"Not a valid param name: {repr(self.name)}")
68
+ if self.default_value is not None and not isinstance(self.default_value, self.type):
69
+ raise TypeError(
70
+ f"Default value for param `{self.name}` must be an instance of {self.type}: {self.default_value}"
71
+ )
72
+
73
+ @property
74
+ def default_value_str(self) -> str | None:
75
+ if self.default_value is None:
76
+ return None
77
+ elif issubclass(self.type, Enum):
78
+ return self.type(self.default_value).name
79
+ else:
80
+ return str(self.default_value)
81
+
82
+ @property
83
+ def valid_values(self) -> list[str]:
84
+ if self.valid_str_values:
85
+ return self.valid_str_values
86
+ elif issubclass(self.type, Enum):
87
+ # Use the enum names as the valid values.
88
+ return [e.name for e in self.type]
89
+ else:
90
+ return [] # Any value is allowed.
91
+
92
+ def validate_value(self, value: Any) -> None:
93
+ """
94
+ For enum or closed str types, validate that the value is in the list of valid values.
95
+ """
96
+ if self.valid_str_values and not self.is_open_ended and value not in self.valid_str_values:
97
+ raise InvalidInput(
98
+ f"Invalid value for parameter `{self.name}`: {value!r} not in allowed str values: {self.valid_str_values}"
99
+ )
100
+ elif issubclass(self.type, Enum) and value not in self.type:
101
+ raise InvalidInput(
102
+ f"Invalid value for parameter `{self.name}`: {value!r} not in allowed enum values: {list(self.type)}"
103
+ )
104
+
105
+ @property
106
+ def full_description(self) -> str:
107
+ desc = self.description or ""
108
+ if desc:
109
+ desc += "\n\n"
110
+ desc += self.valid_and_default_values
111
+ return desc
112
+
113
+ @property
114
+ def valid_and_default_values(self) -> str:
115
+ doc_str = ""
116
+ if self.valid_values:
117
+ val_list = ", ".join(f"`{v}`" for v in self.valid_values)
118
+ if self.is_open_ended:
119
+ doc_str += f"Suggested values (open str type {self.type.__name__}): {val_list}"
120
+ else:
121
+ doc_str += f"Allowed values (type {self.type.__name__}): {val_list}"
122
+ if self.default_value:
123
+ if doc_str:
124
+ doc_str += "\n\n"
125
+ doc_str += f"Default value is: `{self.default_value}`"
126
+ return doc_str
127
+
128
+ @property
129
+ def is_bool(self) -> bool:
130
+ return issubclass(self.type, bool)
131
+
132
+ @property
133
+ def is_path(self) -> bool:
134
+ return issubclass(self.type, Path) or (
135
+ # XXX As a convenience, infer path types from the variable name..
136
+ issubclass(self.type, str) and self.name in ("path", "paths")
137
+ )
138
+
139
+ @property
140
+ def shell_prefix(self) -> str:
141
+ if self.is_bool:
142
+ return f"--{self.name}"
143
+ else:
144
+ return f"--{self.name}="
145
+
146
+ @property
147
+ def display(self) -> str:
148
+ if self.is_bool:
149
+ return f"{self.shell_prefix}"
150
+ else:
151
+ return f"{self.shell_prefix}VALUE"
152
+
153
+ def with_default(self, default: T) -> Param:
154
+ return replace(self, default_value=default)
155
+
156
+ def json_schema(self) -> JsonSchemaValue:
157
+ """
158
+ Generate a JSON schema for this parameter.
159
+ """
160
+ schema: JsonSchemaValue = {
161
+ "title": self.name,
162
+ "description": self.description or "",
163
+ }
164
+
165
+ if issubclass(self.type, bool):
166
+ schema["type"] = "boolean"
167
+ elif issubclass(self.type, int):
168
+ schema["type"] = "integer"
169
+ elif issubclass(self.type, float):
170
+ schema["type"] = "number"
171
+ elif issubclass(self.type, str):
172
+ schema["type"] = "string"
173
+ if self.valid_str_values and not self.is_open_ended:
174
+ schema["enum"] = self.valid_str_values
175
+ elif issubclass(self.type, Enum):
176
+ # Enums serialized by value.
177
+ schema["type"] = "string"
178
+ schema["enum"] = [e.value for e in self.type]
179
+ else:
180
+ # Default to string for complex types.
181
+ schema["type"] = "string"
182
+
183
+ if self.default_value is not None:
184
+ if issubclass(self.type, Enum):
185
+ # Enums serialized by value.
186
+ schema["default"] = self.type(self.default_value).value
187
+ else:
188
+ schema["default"] = self.default_value
189
+
190
+ return schema
191
+
192
+
193
+ RawParamValue = str | bool
194
+ """
195
+ Serialized string or boolean value for a parameter. May be converted to another type
196
+ like an enum. This type is compatible with command-line option values.
197
+ """
198
+
199
+
200
+ ParamDeclarations: TypeAlias = tuple[Param, ...]
201
+ """
202
+ A list of parameter declarations, possibly with default values.
203
+ """
204
+
205
+
206
+ # These are the default models for typical use cases.
207
+ # The user may override them with parameters.
208
+ DEFAULT_CAREFUL_LLM = LLM.o1_preview
209
+ DEFAULT_STRUCTURED_LLM = LLM.gpt_4o
210
+ DEFAULT_BASIC_LLM = LLM.claude_3_7_sonnet
211
+ DEFAULT_FAST_LLM = LLM.claude_3_5_haiku
212
+
213
+
214
+ # Parameters set globally such as in the workspace.
215
+ GLOBAL_PARAMS: dict[str, Param] = {
216
+ "careful_llm": Param(
217
+ "careful_llm",
218
+ "Default LLM used for complex, unstructured requests (including for the kash assistant).",
219
+ default_value=DEFAULT_CAREFUL_LLM,
220
+ type=LLMName,
221
+ valid_str_values=list(LLM),
222
+ is_open_ended=True,
223
+ ),
224
+ "structured_llm": Param(
225
+ "structured_llm",
226
+ "Default LLM used for complex, structured requests (including for the kash assistant).",
227
+ default_value=DEFAULT_STRUCTURED_LLM,
228
+ type=LLMName,
229
+ valid_str_values=list(LLM),
230
+ is_open_ended=True,
231
+ ),
232
+ "basic_llm": Param(
233
+ "basic_llm",
234
+ "Default LLM used for basic requests (including for the kash assistant).",
235
+ default_value=DEFAULT_BASIC_LLM,
236
+ type=LLMName,
237
+ valid_str_values=list(LLM),
238
+ is_open_ended=True,
239
+ ),
240
+ "fast_llm": Param(
241
+ "fast_llm",
242
+ "Default LLM used for fast responses (including for the kash assistant).",
243
+ default_value=DEFAULT_FAST_LLM,
244
+ type=LLMName,
245
+ valid_str_values=list(LLM),
246
+ is_open_ended=True,
247
+ ),
248
+ }
249
+
250
+ # Parameters that are common to all actions.
251
+ COMMON_ACTION_PARAMS: dict[str, Param] = {
252
+ "model": Param(
253
+ "model",
254
+ "The name of the LLM.",
255
+ default_value=None, # Let actions set defaults.
256
+ type=LLMName,
257
+ valid_str_values=list(LLM),
258
+ is_open_ended=True,
259
+ ),
260
+ "language": Param(
261
+ "language",
262
+ "The language of the input audio or text.",
263
+ type=str,
264
+ default_value=None,
265
+ valid_str_values=LANGUAGE_LIST,
266
+ ),
267
+ "chunk_size": Param(
268
+ "chunk_size",
269
+ "For actions that support it, process chunks of what size?",
270
+ default_value=None,
271
+ type=int,
272
+ ),
273
+ "chunk_unit": Param(
274
+ "chunk_unit",
275
+ "For actions that support it, the unit for measuring chunk size.",
276
+ default_value=None,
277
+ type=TextUnit,
278
+ ),
279
+ "query": Param(
280
+ "query",
281
+ "For search actions, the query to use.",
282
+ type=str,
283
+ default_value=None,
284
+ is_explicit=True,
285
+ ),
286
+ }
287
+
288
+ # Extra parameters that are available when an action is invoked from the shell.
289
+ # Applies globally to all actions.
290
+ RUNTIME_ACTION_PARAMS: dict[str, Param] = {
291
+ "rerun": Param(
292
+ "rerun",
293
+ "Rerun an action that would otherwise be skipped because "
294
+ "it produces an output item that already exists.",
295
+ type=bool,
296
+ ),
297
+ }
298
+
299
+
300
+ USER_SETTABLE_PARAMS: dict[str, Param] = {**GLOBAL_PARAMS, **COMMON_ACTION_PARAMS}
301
+
302
+ ALL_COMMON_PARAMS: dict[str, Param] = {
303
+ **GLOBAL_PARAMS,
304
+ **COMMON_ACTION_PARAMS,
305
+ **RUNTIME_ACTION_PARAMS,
306
+ }
307
+
308
+ HELP_PARAM = Param(
309
+ "help",
310
+ "Show full help for this command or action.",
311
+ type=bool,
312
+ )
313
+
314
+ SHOW_SOURCE_PARAM = Param(
315
+ "show_source",
316
+ "Show the source code for this command or action.",
317
+ type=bool,
318
+ )
319
+
320
+ # Parameters present on all shell commands but not formally options to the
321
+ # commands or actions.
322
+ COMMON_SHELL_PARAMS: dict[str, Param] = {
323
+ "help": HELP_PARAM,
324
+ "show_source": SHOW_SOURCE_PARAM,
325
+ }
326
+
327
+
328
+ def common_param(name: str) -> Param:
329
+ """
330
+ Get a commonly used parameter by name.
331
+ """
332
+ param = ALL_COMMON_PARAMS.get(name)
333
+ if param is None:
334
+ raise InvalidParamName(name)
335
+ return param
336
+
337
+
338
+ def common_params(*names: str) -> tuple[Param, ...]:
339
+ """
340
+ Get a set of commonly used parameters by name.
341
+ """
342
+ return tuple(common_param(name) for name in names)
343
+
344
+
345
+ @dataclass
346
+ class RawParamValues:
347
+ """
348
+ A set of raw parameter values in raw (mostly untyped string or bool) format,
349
+ as they are read from the shell.
350
+ """
351
+
352
+ values: dict[str, RawParamValue] = field(default_factory=dict)
353
+
354
+ def items(self):
355
+ return self.values.items()
356
+
357
+ def get_parsed_value(
358
+ self, param_name: str, type: type[T], param_info: dict[str, Param]
359
+ ) -> T | None:
360
+ raw_value = self.values.get(param_name)
361
+ if raw_value is None:
362
+ param = param_info.get(param_name)
363
+ if param:
364
+ return param.default_value
365
+ else:
366
+ raise InvalidParamName(param_name)
367
+ else:
368
+ return instantiate_as_type(raw_value, type)
369
+
370
+ def parse_all(self, param_info: dict[str, Param]) -> TypedParamValues:
371
+ """
372
+ Convert and validate all raw values to typed values, using the provided parameter info.
373
+ Any extra params in the provided `param_info` are ignored.
374
+ """
375
+ applicable_params = {
376
+ name: param for name, param in param_info.items() if name in self.values.keys()
377
+ }
378
+ if len(applicable_params) != len(self.values):
379
+ raise InvalidInput(
380
+ f"Did not find matching params for the given parameter names: "
381
+ f"{', '.join(repr(k) for k in self.values.keys() - applicable_params.keys())}"
382
+ )
383
+
384
+ values = {
385
+ name: self.get_parsed_value(name, param.type, param_info)
386
+ for name, param in applicable_params.items()
387
+ }
388
+ typed_values = TypedParamValues(values=values, params=applicable_params)
389
+
390
+ log.info("Raw params: %s", self)
391
+ log.info("Parsed params: %s", typed_values)
392
+ return typed_values
393
+
394
+ def as_str(self) -> str:
395
+ if self.items():
396
+ return fmt_lines([format_key_value(name, value) for name, value in self.items()])
397
+ else:
398
+ return fmt_lines(["(no parameters)"])
399
+
400
+ def as_str_brief(self):
401
+ return str(self.values)
402
+
403
+ def __str__(self):
404
+ return self.as_str_brief()
405
+
406
+
407
+ @dataclass(frozen=True)
408
+ class TypedParamValues:
409
+ """
410
+ A set of parameter values in typed format, with parameter info for
411
+ each value.
412
+ """
413
+
414
+ def __post_init__(self):
415
+ if set(self.values.keys()) != set(self.params.keys()):
416
+ raise ValueError(
417
+ f"Parameter names in values and params must match: "
418
+ f"{sorted(self.values.keys())} != {sorted(self.params.keys())}"
419
+ )
420
+
421
+ @staticmethod
422
+ def create(values: dict[str, Any], param_info: Iterable[Param]) -> TypedParamValues:
423
+ """
424
+ Create a `TypedParamValues`, checking that all values have corresponding
425
+ param info.
426
+ """
427
+ params = {p.name: p for p in param_info if p.name in values}
428
+ if set(params.keys()) != set(values.keys()):
429
+ raise ValueError(
430
+ f"Missing params for supplied values: values for {sorted(values.keys())} but params for {sorted(params.keys())}"
431
+ )
432
+ return TypedParamValues(values=values, params=params)
433
+
434
+ values: dict[str, Any] = field(default_factory=dict)
435
+ params: dict[str, Param] = field(default_factory=dict)