zrb 1.8.10__py3-none-any.whl → 1.21.29__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of zrb might be problematic. Click here for more details.

Files changed (147) hide show
  1. zrb/__init__.py +126 -113
  2. zrb/__main__.py +1 -1
  3. zrb/attr/type.py +10 -7
  4. zrb/builtin/__init__.py +2 -50
  5. zrb/builtin/git.py +12 -1
  6. zrb/builtin/group.py +31 -15
  7. zrb/builtin/http.py +7 -8
  8. zrb/builtin/llm/attachment.py +40 -0
  9. zrb/builtin/llm/chat_completion.py +274 -0
  10. zrb/builtin/llm/chat_session.py +152 -85
  11. zrb/builtin/llm/chat_session_cmd.py +288 -0
  12. zrb/builtin/llm/chat_trigger.py +79 -0
  13. zrb/builtin/llm/history.py +7 -9
  14. zrb/builtin/llm/llm_ask.py +221 -98
  15. zrb/builtin/llm/tool/api.py +74 -52
  16. zrb/builtin/llm/tool/cli.py +46 -17
  17. zrb/builtin/llm/tool/code.py +71 -90
  18. zrb/builtin/llm/tool/file.py +301 -241
  19. zrb/builtin/llm/tool/note.py +84 -0
  20. zrb/builtin/llm/tool/rag.py +38 -8
  21. zrb/builtin/llm/tool/sub_agent.py +67 -50
  22. zrb/builtin/llm/tool/web.py +146 -122
  23. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +7 -7
  24. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +5 -5
  25. zrb/builtin/project/add/fastapp/fastapp_util.py +1 -1
  26. zrb/builtin/searxng/config/settings.yml +5671 -0
  27. zrb/builtin/searxng/start.py +21 -0
  28. zrb/builtin/setup/latex/ubuntu.py +1 -0
  29. zrb/builtin/setup/ubuntu.py +1 -1
  30. zrb/builtin/shell/autocomplete/bash.py +4 -3
  31. zrb/builtin/shell/autocomplete/zsh.py +4 -3
  32. zrb/builtin/todo.py +13 -2
  33. zrb/config/config.py +614 -0
  34. zrb/config/default_prompt/file_extractor_system_prompt.md +112 -0
  35. zrb/config/default_prompt/interactive_system_prompt.md +29 -0
  36. zrb/config/default_prompt/persona.md +1 -0
  37. zrb/config/default_prompt/repo_extractor_system_prompt.md +112 -0
  38. zrb/config/default_prompt/repo_summarizer_system_prompt.md +29 -0
  39. zrb/config/default_prompt/summarization_prompt.md +57 -0
  40. zrb/config/default_prompt/system_prompt.md +38 -0
  41. zrb/config/llm_config.py +339 -0
  42. zrb/config/llm_context/config.py +166 -0
  43. zrb/config/llm_context/config_parser.py +40 -0
  44. zrb/config/llm_context/workflow.py +81 -0
  45. zrb/config/llm_rate_limitter.py +190 -0
  46. zrb/{runner → config}/web_auth_config.py +17 -22
  47. zrb/context/any_shared_context.py +17 -1
  48. zrb/context/context.py +16 -2
  49. zrb/context/shared_context.py +18 -8
  50. zrb/group/any_group.py +12 -5
  51. zrb/group/group.py +67 -3
  52. zrb/input/any_input.py +5 -1
  53. zrb/input/base_input.py +18 -6
  54. zrb/input/option_input.py +13 -1
  55. zrb/input/text_input.py +8 -25
  56. zrb/runner/cli.py +25 -23
  57. zrb/runner/common_util.py +24 -19
  58. zrb/runner/web_app.py +3 -3
  59. zrb/runner/web_route/docs_route.py +1 -1
  60. zrb/runner/web_route/error_page/serve_default_404.py +1 -1
  61. zrb/runner/web_route/error_page/show_error_page.py +1 -1
  62. zrb/runner/web_route/home_page/home_page_route.py +2 -2
  63. zrb/runner/web_route/login_api_route.py +1 -1
  64. zrb/runner/web_route/login_page/login_page_route.py +2 -2
  65. zrb/runner/web_route/logout_api_route.py +1 -1
  66. zrb/runner/web_route/logout_page/logout_page_route.py +2 -2
  67. zrb/runner/web_route/node_page/group/show_group_page.py +1 -1
  68. zrb/runner/web_route/node_page/node_page_route.py +1 -1
  69. zrb/runner/web_route/node_page/task/show_task_page.py +1 -1
  70. zrb/runner/web_route/refresh_token_api_route.py +1 -1
  71. zrb/runner/web_route/static/static_route.py +1 -1
  72. zrb/runner/web_route/task_input_api_route.py +6 -6
  73. zrb/runner/web_route/task_session_api_route.py +20 -12
  74. zrb/runner/web_util/cookie.py +1 -1
  75. zrb/runner/web_util/token.py +1 -1
  76. zrb/runner/web_util/user.py +8 -4
  77. zrb/session/any_session.py +24 -17
  78. zrb/session/session.py +50 -25
  79. zrb/session_state_logger/any_session_state_logger.py +9 -4
  80. zrb/session_state_logger/file_session_state_logger.py +16 -6
  81. zrb/session_state_logger/session_state_logger_factory.py +1 -1
  82. zrb/task/any_task.py +30 -9
  83. zrb/task/base/context.py +17 -9
  84. zrb/task/base/execution.py +15 -8
  85. zrb/task/base/lifecycle.py +8 -4
  86. zrb/task/base/monitoring.py +12 -7
  87. zrb/task/base_task.py +69 -5
  88. zrb/task/base_trigger.py +12 -5
  89. zrb/task/cmd_task.py +1 -1
  90. zrb/task/llm/agent.py +154 -161
  91. zrb/task/llm/agent_runner.py +152 -0
  92. zrb/task/llm/config.py +47 -18
  93. zrb/task/llm/conversation_history.py +209 -0
  94. zrb/task/llm/conversation_history_model.py +67 -0
  95. zrb/task/llm/default_workflow/coding/workflow.md +41 -0
  96. zrb/task/llm/default_workflow/copywriting/workflow.md +68 -0
  97. zrb/task/llm/default_workflow/git/workflow.md +118 -0
  98. zrb/task/llm/default_workflow/golang/workflow.md +128 -0
  99. zrb/task/llm/default_workflow/html-css/workflow.md +135 -0
  100. zrb/task/llm/default_workflow/java/workflow.md +146 -0
  101. zrb/task/llm/default_workflow/javascript/workflow.md +158 -0
  102. zrb/task/llm/default_workflow/python/workflow.md +160 -0
  103. zrb/task/llm/default_workflow/researching/workflow.md +153 -0
  104. zrb/task/llm/default_workflow/rust/workflow.md +162 -0
  105. zrb/task/llm/default_workflow/shell/workflow.md +299 -0
  106. zrb/task/llm/error.py +24 -10
  107. zrb/task/llm/file_replacement.py +206 -0
  108. zrb/task/llm/file_tool_model.py +57 -0
  109. zrb/task/llm/history_processor.py +206 -0
  110. zrb/task/llm/history_summarization.py +11 -166
  111. zrb/task/llm/print_node.py +193 -69
  112. zrb/task/llm/prompt.py +242 -45
  113. zrb/task/llm/subagent_conversation_history.py +41 -0
  114. zrb/task/llm/tool_wrapper.py +260 -57
  115. zrb/task/llm/workflow.py +76 -0
  116. zrb/task/llm_task.py +182 -171
  117. zrb/task/make_task.py +2 -3
  118. zrb/task/rsync_task.py +26 -11
  119. zrb/task/scheduler.py +4 -4
  120. zrb/util/attr.py +54 -39
  121. zrb/util/callable.py +23 -0
  122. zrb/util/cli/markdown.py +12 -0
  123. zrb/util/cli/text.py +30 -0
  124. zrb/util/file.py +29 -11
  125. zrb/util/git.py +8 -11
  126. zrb/util/git_diff_model.py +10 -0
  127. zrb/util/git_subtree.py +9 -14
  128. zrb/util/git_subtree_model.py +32 -0
  129. zrb/util/init_path.py +1 -1
  130. zrb/util/markdown.py +62 -0
  131. zrb/util/string/conversion.py +2 -2
  132. zrb/util/todo.py +17 -50
  133. zrb/util/todo_model.py +46 -0
  134. zrb/util/truncate.py +23 -0
  135. zrb/util/yaml.py +204 -0
  136. zrb/xcom/xcom.py +10 -0
  137. zrb-1.21.29.dist-info/METADATA +270 -0
  138. {zrb-1.8.10.dist-info → zrb-1.21.29.dist-info}/RECORD +140 -98
  139. {zrb-1.8.10.dist-info → zrb-1.21.29.dist-info}/WHEEL +1 -1
  140. zrb/config.py +0 -335
  141. zrb/llm_config.py +0 -411
  142. zrb/llm_rate_limitter.py +0 -125
  143. zrb/task/llm/context.py +0 -102
  144. zrb/task/llm/context_enrichment.py +0 -199
  145. zrb/task/llm/history.py +0 -211
  146. zrb-1.8.10.dist-info/METADATA +0 -264
  147. {zrb-1.8.10.dist-info → zrb-1.21.29.dist-info}/entry_points.txt +0 -0
@@ -1,114 +1,257 @@
1
+ import os
2
+ from collections.abc import Callable
3
+ from typing import TYPE_CHECKING
4
+
1
5
  from zrb.builtin.group import llm_group
6
+ from zrb.builtin.llm.attachment import get_media_type
2
7
  from zrb.builtin.llm.chat_session import get_llm_ask_input_mapping, read_user_prompt
3
8
  from zrb.builtin.llm.history import read_chat_conversation, write_chat_conversation
4
9
  from zrb.builtin.llm.input import PreviousSessionInput
5
- from zrb.builtin.llm.tool.api import get_current_location, get_current_weather
10
+ from zrb.builtin.llm.tool.api import (
11
+ create_get_current_location,
12
+ create_get_current_weather,
13
+ )
6
14
  from zrb.builtin.llm.tool.cli import run_shell_command
7
15
  from zrb.builtin.llm.tool.code import analyze_repo
8
16
  from zrb.builtin.llm.tool.file import (
9
17
  analyze_file,
10
- apply_diff,
11
18
  list_files,
12
19
  read_from_file,
20
+ replace_in_file,
13
21
  search_files,
14
22
  write_to_file,
15
23
  )
24
+ from zrb.builtin.llm.tool.note import (
25
+ read_contextual_note,
26
+ read_long_term_note,
27
+ write_contextual_note,
28
+ write_long_term_note,
29
+ )
16
30
  from zrb.builtin.llm.tool.web import (
17
31
  create_search_internet_tool,
18
32
  open_web_page,
19
- search_arxiv,
20
- search_wikipedia,
21
33
  )
22
34
  from zrb.callback.callback import Callback
23
- from zrb.config import CFG
35
+ from zrb.config.config import CFG
36
+ from zrb.config.llm_config import llm_config
37
+ from zrb.context.any_context import AnyContext
38
+ from zrb.input.any_input import AnyInput
24
39
  from zrb.input.bool_input import BoolInput
25
40
  from zrb.input.str_input import StrInput
26
41
  from zrb.input.text_input import TextInput
27
42
  from zrb.task.base_trigger import BaseTrigger
28
43
  from zrb.task.llm_task import LLMTask
44
+ from zrb.util.string.conversion import to_boolean
29
45
 
30
- _llm_ask_inputs = [
31
- StrInput(
32
- "model",
33
- description="LLM Model",
34
- prompt="LLM Model",
35
- default="",
36
- allow_positional_parsing=False,
37
- always_prompt=False,
38
- allow_empty=True,
39
- ),
40
- StrInput(
41
- "base-url",
42
- description="LLM API Base URL",
43
- prompt="LLM API Base URL",
44
- default="",
45
- allow_positional_parsing=False,
46
- always_prompt=False,
47
- allow_empty=True,
48
- ),
49
- StrInput(
50
- "api-key",
51
- description="LLM API Key",
52
- prompt="LLM API Key",
53
- default="",
54
- allow_positional_parsing=False,
55
- always_prompt=False,
56
- allow_empty=True,
57
- ),
58
- TextInput(
59
- "system-prompt",
60
- description="System prompt",
61
- prompt="System prompt",
62
- default="",
63
- allow_positional_parsing=False,
64
- always_prompt=False,
65
- ),
66
- BoolInput(
67
- "start-new",
68
- description="Start new conversation (LLM will forget everything)",
69
- prompt="Start new conversation (LLM will forget everything)",
70
- default=False,
71
- allow_positional_parsing=False,
72
- always_prompt=False,
73
- ),
74
- TextInput("message", description="User message", prompt="Your message"),
75
- PreviousSessionInput(
76
- "previous-session",
77
- description="Previous conversation session",
78
- prompt="Previous conversation session (can be empty)",
79
- allow_positional_parsing=False,
80
- allow_empty=True,
81
- always_prompt=False,
82
- ),
83
- ]
84
-
85
- llm_ask: LLMTask = llm_group.add_task(
86
- LLMTask(
87
- name="llm-ask",
88
- input=_llm_ask_inputs,
89
- description="❓ Ask LLM",
90
- model=lambda ctx: None if ctx.input.model.strip() == "" else ctx.input.model,
91
- model_base_url=lambda ctx: (
92
- None if ctx.input.base_url.strip() == "" else ctx.input.base_url
46
+ if TYPE_CHECKING:
47
+ from pydantic_ai import AbstractToolset, Tool, UserContent
48
+
49
+ ToolOrCallable = Tool | Callable
50
+
51
+
52
+ def _get_toolset(ctx: AnyContext) -> list["AbstractToolset[None] | str"]:
53
+ cwd = os.getcwd()
54
+ toolsets = []
55
+ for config_path in [
56
+ os.path.join(cwd, "mcp_config.json"),
57
+ os.path.join(cwd, "mcp-config.json"),
58
+ ]:
59
+ if os.path.isfile(config_path):
60
+ toolsets.append(config_path)
61
+ return toolsets
62
+
63
+
64
+ def _get_tool(ctx: AnyContext) -> list["ToolOrCallable"]:
65
+ tools = [
66
+ read_long_term_note,
67
+ write_long_term_note,
68
+ read_contextual_note,
69
+ write_contextual_note,
70
+ ]
71
+ if CFG.LLM_ALLOW_ANALYZE_REPO:
72
+ tools.append(analyze_repo)
73
+ if CFG.LLM_ALLOW_ANALYZE_FILE:
74
+ tools.append(analyze_file)
75
+ if CFG.LLM_ALLOW_ACCESS_LOCAL_FILE:
76
+ tools.append(search_files)
77
+ tools.append(list_files)
78
+ tools.append(read_from_file)
79
+ tools.append(replace_in_file)
80
+ tools.append(write_to_file)
81
+ if CFG.LLM_ALLOW_ACCESS_SHELL:
82
+ tools.append(run_shell_command)
83
+ if CFG.LLM_ALLOW_OPEN_WEB_PAGE:
84
+ tools.append(open_web_page)
85
+ if CFG.LLM_ALLOW_GET_CURRENT_LOCATION:
86
+ tools.append(create_get_current_location())
87
+ if CFG.LLM_ALLOW_GET_CURRENT_WEATHER:
88
+ tools.append(create_get_current_weather())
89
+ if CFG.SERPAPI_KEY != "" and CFG.LLM_ALLOW_SEARCH_INTERNET:
90
+ tools.append(create_search_internet_tool())
91
+ return tools
92
+
93
+
94
+ def _get_default_yolo_mode(ctx: AnyContext) -> str:
95
+ default_value = llm_config.default_yolo_mode
96
+ if isinstance(default_value, list):
97
+ return ",".join(default_value)
98
+ return f"{default_value}"
99
+
100
+
101
+ def _render_yolo_mode_input(ctx: AnyContext) -> list[str] | bool:
102
+ if ctx.input.yolo.strip() == "":
103
+ return []
104
+ elements = [element.strip() for element in ctx.input.yolo.split(",")]
105
+ if len(elements) == 1:
106
+ try:
107
+ return to_boolean(elements[0])
108
+ except Exception:
109
+ pass
110
+ return elements
111
+
112
+
113
+ def _render_attach_input(ctx: AnyContext) -> "list[UserContent]":
114
+ from pathlib import Path
115
+
116
+ from pydantic_ai import BinaryContent
117
+
118
+ attachment_paths: list[str] = [
119
+ attachment_path.strip()
120
+ for attachment_path in ctx.input.attach.split(",")
121
+ if attachment_path.strip() != ""
122
+ ]
123
+ if len(attachment_paths) == 0:
124
+ return []
125
+ attachments = []
126
+ for attachment_path in attachment_paths:
127
+ attachment_path = os.path.abspath(os.path.expanduser(attachment_path))
128
+ media_type = get_media_type(attachment_path)
129
+ if media_type is None:
130
+ ctx.log_error(f"Cannot get media type of {attachment_path}")
131
+ continue
132
+ data = Path(attachment_path).read_bytes()
133
+ attachments.append(BinaryContent(data, media_type=media_type))
134
+ return attachments
135
+
136
+
137
+ def _get_inputs(require_message: bool = True) -> list[AnyInput | None]:
138
+ return [
139
+ StrInput(
140
+ "model",
141
+ description="LLM Model",
142
+ prompt="LLM Model",
143
+ default="",
144
+ allow_positional_parsing=False,
145
+ always_prompt=False,
146
+ allow_empty=True,
93
147
  ),
94
- model_api_key=lambda ctx: (
95
- None if ctx.input.api_key.strip() == "" else ctx.input.api_key
148
+ StrInput(
149
+ "base-url",
150
+ description="LLM API Base URL",
151
+ prompt="LLM API Base URL",
152
+ default="",
153
+ allow_positional_parsing=False,
154
+ always_prompt=False,
155
+ allow_empty=True,
96
156
  ),
97
- conversation_history_reader=read_chat_conversation,
98
- conversation_history_writer=write_chat_conversation,
99
- system_prompt=lambda ctx: (
100
- None if ctx.input.system_prompt.strip() == "" else ctx.input.system_prompt
157
+ StrInput(
158
+ "api-key",
159
+ description="LLM API Key",
160
+ prompt="LLM API Key",
161
+ default="",
162
+ allow_positional_parsing=False,
163
+ always_prompt=False,
164
+ allow_empty=True,
101
165
  ),
102
- message="{ctx.input.message}",
103
- retries=0,
166
+ TextInput(
167
+ "system-prompt",
168
+ description="System prompt",
169
+ prompt="System prompt",
170
+ default="",
171
+ allow_positional_parsing=False,
172
+ always_prompt=False,
173
+ ),
174
+ TextInput(
175
+ "workflows",
176
+ description="Workflows",
177
+ prompt="Workflows",
178
+ default=lambda ctx: ",".join(llm_config.default_workflows),
179
+ allow_positional_parsing=False,
180
+ always_prompt=False,
181
+ ),
182
+ BoolInput(
183
+ "start-new",
184
+ description="Start new session (LLM Agent will forget past conversation)",
185
+ prompt="Start new session (LLM Agent will forget past conversation)",
186
+ default=False,
187
+ allow_positional_parsing=False,
188
+ always_prompt=False,
189
+ ),
190
+ StrInput(
191
+ "yolo",
192
+ description="YOLO mode (LLM Agent will start in YOLO Mode)",
193
+ prompt="YOLO mode (LLM Agent will start in YOLO Mode)",
194
+ default=_get_default_yolo_mode,
195
+ allow_positional_parsing=False,
196
+ always_prompt=False,
197
+ ),
198
+ TextInput(
199
+ "message",
200
+ description="User message",
201
+ prompt="Your message",
202
+ always_prompt=require_message,
203
+ allow_empty=not require_message,
204
+ ),
205
+ StrInput(
206
+ name="attach",
207
+ description="Comma separated attachments",
208
+ default="",
209
+ allow_positional_parsing=False,
210
+ always_prompt=False,
211
+ ),
212
+ PreviousSessionInput(
213
+ "previous-session",
214
+ description="Previous conversation session",
215
+ prompt="Previous conversation session (can be empty)",
216
+ allow_positional_parsing=False,
217
+ allow_empty=True,
218
+ always_prompt=False,
219
+ ),
220
+ ]
221
+
222
+
223
+ llm_ask = LLMTask(
224
+ name="llm-ask",
225
+ input=_get_inputs(True),
226
+ description="❓ Ask LLM",
227
+ model=lambda ctx: None if ctx.input.model.strip() == "" else ctx.input.model,
228
+ model_base_url=lambda ctx: (
229
+ None if ctx.input.base_url.strip() == "" else ctx.input.base_url
104
230
  ),
105
- alias="ask",
231
+ model_api_key=lambda ctx: (
232
+ None if ctx.input.api_key.strip() == "" else ctx.input.api_key
233
+ ),
234
+ conversation_history_reader=read_chat_conversation,
235
+ conversation_history_writer=write_chat_conversation,
236
+ system_prompt=lambda ctx: (
237
+ None if ctx.input.system_prompt.strip() == "" else ctx.input.system_prompt
238
+ ),
239
+ workflows=lambda ctx: (
240
+ None if ctx.input.workflows.strip() == "" else ctx.input.workflows.split(",")
241
+ ),
242
+ attachment=_render_attach_input,
243
+ message="{ctx.input.message}",
244
+ tools=_get_tool,
245
+ toolsets=_get_toolset,
246
+ yolo_mode=_render_yolo_mode_input,
247
+ retries=0,
106
248
  )
249
+ llm_group.add_task(llm_ask, alias="ask")
107
250
 
108
251
  llm_group.add_task(
109
252
  BaseTrigger(
110
253
  name="llm-chat",
111
- input=_llm_ask_inputs,
254
+ input=_get_inputs(False),
112
255
  description="💬 Chat with LLM",
113
256
  queue_name="ask_trigger",
114
257
  action=read_user_prompt,
@@ -124,23 +267,3 @@ llm_group.add_task(
124
267
  ),
125
268
  alias="chat",
126
269
  )
127
-
128
- if CFG.LLM_ALLOW_ACCESS_LOCAL_FILE:
129
- llm_ask.append_tool(
130
- list_files,
131
- read_from_file,
132
- write_to_file,
133
- search_files,
134
- apply_diff,
135
- analyze_file,
136
- analyze_repo,
137
- )
138
-
139
- if CFG.LLM_ALLOW_ACCESS_SHELL:
140
- llm_ask.append_tool(run_shell_command)
141
-
142
- if CFG.LLM_ALLOW_ACCESS_INTERNET:
143
- llm_ask.append_tool(open_web_page, search_wikipedia, search_arxiv)
144
- if CFG.SERPAPI_KEY != "":
145
- llm_ask.append_tool(create_search_internet_tool(CFG.SERPAPI_KEY))
146
- llm_ask.append_tool(get_current_location, get_current_weather)
@@ -1,53 +1,75 @@
1
- import json
2
- from typing import Literal
3
-
4
-
5
- def get_current_location() -> str:
6
- """Get current location (latitude, longitude) based on IP address.
7
- Returns:
8
- str: JSON string representing latitude and longitude.
9
- Raises:
10
- requests.RequestException: If the API request fails.
11
- """
12
- import requests
13
-
14
- try:
15
- response = requests.get("http://ip-api.com/json?fields=lat,lon", timeout=5)
16
- response.raise_for_status()
17
- return json.dumps(response.json())
18
- except requests.RequestException as e:
19
- raise requests.RequestException(f"Failed to get location: {e}") from None
20
-
21
-
22
- def get_current_weather(
23
- latitude: float,
24
- longitude: float,
25
- temperature_unit: Literal["celsius", "fahrenheit"],
26
- ) -> str:
27
- """Get current weather for a specific location.
28
- Args:
29
- latitude (float): Latitude coordinate.
30
- longitude (float): Longitude coordinate.
31
- temperature_unit (Literal["celsius", "fahrenheit"]): Temperature unit.
32
- Returns:
33
- str: JSON string with weather data.
34
- Raises:
35
- requests.RequestException: If the API request fails.
36
- """
37
- import requests
38
-
39
- try:
40
- response = requests.get(
41
- "https://api.open-meteo.com/v1/forecast",
42
- params={
43
- "latitude": latitude,
44
- "longitude": longitude,
45
- "temperature_unit": temperature_unit,
46
- "current_weather": True,
47
- },
48
- timeout=5,
1
+ from typing import Any, Callable, Literal
2
+
3
+ from zrb.config.llm_config import llm_config
4
+
5
+
6
+ def create_get_current_location() -> Callable:
7
+ if llm_config.default_current_location_tool is not None:
8
+ return llm_config.default_current_location_tool
9
+
10
+ def get_current_location() -> dict[str, float]:
11
+ """
12
+ Gets the user's current geographical location (latitude and longitude).
13
+
14
+ Example:
15
+ get_current_location() # Returns {'lat': 48.8584, 'lon': 2.2945}
16
+
17
+ Returns:
18
+ dict: Dictionary with 'lat' and 'lon' keys.
19
+ """
20
+ import requests
21
+
22
+ try:
23
+ response = requests.get("http://ip-api.com/json?fields=lat,lon", timeout=5)
24
+ response.raise_for_status()
25
+ return dict(response.json())
26
+ except requests.RequestException as e:
27
+ raise requests.RequestException(f"Failed to get location: {e}")
28
+
29
+ return get_current_location
30
+
31
+
32
+ def create_get_current_weather() -> Callable:
33
+ if llm_config.default_current_weather_tool is not None:
34
+ return llm_config.default_current_weather_tool
35
+
36
+ def get_current_weather(
37
+ latitude: float,
38
+ longitude: float,
39
+ temperature_unit: Literal["celsius", "fahrenheit"],
40
+ ) -> dict[str, Any]:
41
+ """
42
+ Gets current weather conditions for a given location.
43
+
44
+ Example:
45
+ get_current_weather(
46
+ latitude=34.0522, longitude=-118.2437, temperature_unit='fahrenheit'
49
47
  )
50
- response.raise_for_status()
51
- return json.dumps(response.json())
52
- except requests.RequestException as e:
53
- raise requests.RequestException(f"Failed to get weather data: {e}") from None
48
+
49
+ Args:
50
+ latitude (float): Latitude of the location.
51
+ longitude (float): Longitude of the location.
52
+ temperature_unit (Literal): 'celsius' or 'fahrenheit'.
53
+
54
+ Returns:
55
+ dict: Detailed weather data.
56
+ """
57
+ import requests
58
+
59
+ try:
60
+ response = requests.get(
61
+ "https://api.open-meteo.com/v1/forecast",
62
+ params={
63
+ "latitude": latitude,
64
+ "longitude": longitude,
65
+ "temperature_unit": temperature_unit,
66
+ "current_weather": True,
67
+ },
68
+ timeout=5,
69
+ )
70
+ response.raise_for_status()
71
+ return dict(response.json())
72
+ except requests.RequestException as e:
73
+ raise requests.RequestException(f"Failed to get weather data: {e}")
74
+
75
+ return get_current_weather
@@ -1,23 +1,52 @@
1
1
  import subprocess
2
+ import sys
2
3
 
4
+ if sys.version_info >= (3, 12):
5
+ from typing import TypedDict
6
+ else:
7
+ from typing_extensions import TypedDict
8
+
9
+
10
+ class ShellCommandResult(TypedDict):
11
+ """
12
+ Result of shell command execution
13
+
14
+ Attributes:
15
+ return_code: The return code, 0 indicating no error
16
+ stdout: Standard output
17
+ stderr: Standard error
18
+ """
19
+
20
+ return_code: int
21
+ stdout: str
22
+ stderr: str
23
+
24
+
25
+ def run_shell_command(command: str) -> ShellCommandResult:
26
+ """
27
+ Executes a non-interactive shell command on the user's machine.
28
+
29
+ CRITICAL: This tool runs with user-level permissions. Explain commands that modify
30
+ the system (e.g., `git`, `pip`) and ask for confirmation.
31
+ IMPORTANT: Long-running processes should be run in the background (e.g., `command &`).
32
+
33
+ Example:
34
+ run_shell_command(command='ls -l')
3
35
 
4
- def run_shell_command(command: str) -> str:
5
- """Execute a shell command and return its combined stdout and stderr.
6
36
  Args:
7
- command (str): Shell command to execute on the user's system.
37
+ command (str): The exact shell command to be executed.
38
+
8
39
  Returns:
9
- str: The command's output (stdout and stderr combined).
10
- Raises:
11
- subprocess.CalledProcessError: If the command returns a non-zero exit code.
12
- subprocess.SubprocessError: If there's an issue with subprocess execution.
40
+ dict: return_code, stdout, and stderr.
13
41
  """
14
- try:
15
- output = subprocess.check_output(
16
- command, shell=True, stderr=subprocess.STDOUT, text=True
17
- )
18
- return output
19
- except subprocess.CalledProcessError as e:
20
- # Include the error output in the exception message
21
- raise subprocess.CalledProcessError(
22
- e.returncode, e.cmd, e.output, e.stderr
23
- ) from None
42
+ result = subprocess.run(
43
+ command,
44
+ shell=True,
45
+ capture_output=True,
46
+ text=True,
47
+ )
48
+ return {
49
+ "return_code": result.returncode,
50
+ "stdout": result.stdout,
51
+ "stderr": result.stderr,
52
+ }