oats-coder 1.0.2__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 (242) hide show
  1. oats/AGENT.dir.python.tools.json +1 -0
  2. oats/AGENT.python.tools.md +131 -0
  3. oats/agent/AGENT.dir.python.tools.json +1 -0
  4. oats/agent/AGENT.python.tools.md +19 -0
  5. oats/agent/agent.py +176 -0
  6. oats/agent/agent.py.AGENT.python.tools.json +7 -0
  7. oats/agent_get_tool_choices_for_prompt.py +32 -0
  8. oats/agent_get_tool_choices_for_prompt.py.AGENT.python.tools.json +7 -0
  9. oats/call_tool_with_loader1.py +430 -0
  10. oats/call_tool_with_loader1.py.AGENT.python.tools.json +7 -0
  11. oats/cli/AGENT.dir.python.tools.json +1 -0
  12. oats/cli/AGENT.python.tools.md +33 -0
  13. oats/cli/approval.py +154 -0
  14. oats/cli/approval.py.AGENT.python.tools.json +7 -0
  15. oats/cli/check_providers.py +25 -0
  16. oats/cli/check_providers.py.AGENT.python.tools.json +7 -0
  17. oats/cli/interactive.py +550 -0
  18. oats/cli/interactive.py.AGENT.python.tools.json +7 -0
  19. oats/cli/process_message.py +153 -0
  20. oats/cli/process_message.py.AGENT.python.tools.json +7 -0
  21. oats/cli/tui/AGENT.dir.python.tools.json +1 -0
  22. oats/cli/tui/AGENT.python.tools.md +19 -0
  23. oats/cli/tui/tui_banner.py +114 -0
  24. oats/cli/tui/tui_banner.py.AGENT.python.tools.json +7 -0
  25. oats/cli/tui/tui_consts.py +120 -0
  26. oats/cli/tui/tui_consts.py.AGENT.python.tools.json +7 -0
  27. oats/cli/tui/tui_utils.py +492 -0
  28. oats/cli/tui/tui_utils.py.AGENT.python.tools.json +7 -0
  29. oats/config/coder.json +27 -0
  30. oats/core/AGENT.dir.python.tools.json +1 -0
  31. oats/core/AGENT.python.tools.md +117 -0
  32. oats/core/__init__.py +19 -0
  33. oats/core/bus.py +170 -0
  34. oats/core/bus.py.AGENT.python.tools.json +7 -0
  35. oats/core/config.py +270 -0
  36. oats/core/config.py.AGENT.python.tools.json +7 -0
  37. oats/core/features.py +118 -0
  38. oats/core/features.py.AGENT.python.tools.json +7 -0
  39. oats/core/id.py +17 -0
  40. oats/core/id.py.AGENT.python.tools.json +7 -0
  41. oats/core/offline.py +93 -0
  42. oats/core/offline.py.AGENT.python.tools.json +7 -0
  43. oats/core/profiles.py +179 -0
  44. oats/core/profiles.py.AGENT.python.tools.json +7 -0
  45. oats/core/storage.py +207 -0
  46. oats/core/storage.py.AGENT.python.tools.json +7 -0
  47. oats/core/tokens.py +80 -0
  48. oats/core/tokens.py.AGENT.python.tools.json +7 -0
  49. oats/date.py +83 -0
  50. oats/date.py.AGENT.python.tools.json +7 -0
  51. oats/determine_best_tools1.py +311 -0
  52. oats/determine_best_tools1.py.AGENT.python.tools.json +7 -0
  53. oats/get_oat_config.py +12 -0
  54. oats/get_oat_config.py.AGENT.python.tools.json +3 -0
  55. oats/git/AGENT.dir.python.tools.json +1 -0
  56. oats/git/AGENT.python.tools.md +89 -0
  57. oats/git/__init__.py +6 -0
  58. oats/git/build_git_repo_to_dataset.py +185 -0
  59. oats/git/build_git_repo_to_dataset.py.AGENT.python.tools.json +7 -0
  60. oats/git/coauthor.py +75 -0
  61. oats/git/coauthor.py.AGENT.python.tools.json +7 -0
  62. oats/git/git_commit_search1.py +537 -0
  63. oats/git/git_commit_search1.py.AGENT.python.tools.json +7 -0
  64. oats/git/git_diff_extractor.py +221 -0
  65. oats/git/git_diff_extractor.py.AGENT.python.tools.json +7 -0
  66. oats/git/git_to_df_converter.py +115 -0
  67. oats/git/git_to_df_converter.py.AGENT.python.tools.json +7 -0
  68. oats/git/repo_to_parquet.py +81 -0
  69. oats/git/repo_to_parquet.py.AGENT.python.tools.json +7 -0
  70. oats/git/walk_up_dir_path_to_find_git_config.py +56 -0
  71. oats/git/walk_up_dir_path_to_find_git_config.py.AGENT.python.tools.json +7 -0
  72. oats/git/worktree.py +184 -0
  73. oats/git/worktree.py.AGENT.python.tools.json +7 -0
  74. oats/hook/AGENT.dir.python.tools.json +1 -0
  75. oats/hook/AGENT.python.tools.md +19 -0
  76. oats/hook/__init__.py +23 -0
  77. oats/hook/engine.py +225 -0
  78. oats/hook/engine.py.AGENT.python.tools.json +7 -0
  79. oats/load_tools_from_source1.py +594 -0
  80. oats/load_tools_from_source1.py.AGENT.python.tools.json +7 -0
  81. oats/log.py +233 -0
  82. oats/log.py.AGENT.python.tools.json +7 -0
  83. oats/lsp/AGENT.dir.python.tools.json +1 -0
  84. oats/lsp/AGENT.python.tools.md +19 -0
  85. oats/lsp/__init__.py +1 -0
  86. oats/lsp/client.py +381 -0
  87. oats/lsp/client.py.AGENT.python.tools.json +7 -0
  88. oats/mcp/AGENT.dir.python.tools.json +1 -0
  89. oats/mcp/AGENT.python.tools.md +159 -0
  90. oats/mcp/config.py +201 -0
  91. oats/mcp/config.py.AGENT.python.tools.json +7 -0
  92. oats/mcp/example_mcp_config.json +58 -0
  93. oats/mcp/fetch.py +405 -0
  94. oats/mcp/fetch.py.AGENT.python.tools.json +7 -0
  95. oats/mcp/index.py +381 -0
  96. oats/mcp/index.py.AGENT.python.tools.json +7 -0
  97. oats/mcp/intent.py +427 -0
  98. oats/mcp/intent.py.AGENT.python.tools.json +7 -0
  99. oats/mcp/models.py +304 -0
  100. oats/mcp/models.py.AGENT.python.tools.json +7 -0
  101. oats/mcp/orchestrator.py +653 -0
  102. oats/mcp/orchestrator.py.AGENT.python.tools.json +7 -0
  103. oats/mcp/ranking.py +243 -0
  104. oats/mcp/ranking.py.AGENT.python.tools.json +7 -0
  105. oats/mcp/registry.py +567 -0
  106. oats/mcp/registry.py.AGENT.python.tools.json +7 -0
  107. oats/mcp/resolver.py +588 -0
  108. oats/mcp/resolver.py.AGENT.python.tools.json +7 -0
  109. oats/mcp/tools.py +574 -0
  110. oats/mcp/tools.py.AGENT.python.tools.json +7 -0
  111. oats/mcp/tracker.py +254 -0
  112. oats/mcp/tracker.py.AGENT.python.tools.json +7 -0
  113. oats/memory/AGENT.dir.python.tools.json +1 -0
  114. oats/memory/AGENT.python.tools.md +19 -0
  115. oats/memory/__init__.py +14 -0
  116. oats/memory/manager.py +180 -0
  117. oats/memory/manager.py.AGENT.python.tools.json +7 -0
  118. oats/memory/models.py +97 -0
  119. oats/memory/models.py.AGENT.python.tools.json +7 -0
  120. oats/models.py +332 -0
  121. oats/models.py.AGENT.python.tools.json +7 -0
  122. oats/oweb/AGENT.dir.python.tools.json +1 -0
  123. oats/oweb/AGENT.python.tools.md +33 -0
  124. oats/oweb/get_auth.py +56 -0
  125. oats/oweb/get_auth.py.AGENT.python.tools.json +7 -0
  126. oats/oweb/login.py +72 -0
  127. oats/oweb/login.py.AGENT.python.tools.json +7 -0
  128. oats/plugins/AGENT.dir.python.tools.json +1 -0
  129. oats/plugins/AGENT.python.tools.md +33 -0
  130. oats/plugins/__init__.py +24 -0
  131. oats/plugins/loader.py +278 -0
  132. oats/plugins/loader.py.AGENT.python.tools.json +7 -0
  133. oats/plugins/manifest.py +171 -0
  134. oats/plugins/manifest.py.AGENT.python.tools.json +7 -0
  135. oats/pp.py +8 -0
  136. oats/pp.py.AGENT.python.tools.json +7 -0
  137. oats/provider/AGENT.dir.python.tools.json +1 -0
  138. oats/provider/AGENT.python.tools.md +33 -0
  139. oats/provider/models.py +249 -0
  140. oats/provider/models.py.AGENT.python.tools.json +7 -0
  141. oats/provider/provider.py +822 -0
  142. oats/provider/provider.py.AGENT.python.tools.json +7 -0
  143. oats/session/AGENT.dir.python.tools.json +1 -0
  144. oats/session/AGENT.python.tools.md +201 -0
  145. oats/session/__init__.py +35 -0
  146. oats/session/build_system_prompt.py +184 -0
  147. oats/session/build_system_prompt.py.AGENT.python.tools.json +7 -0
  148. oats/session/caveman.py +177 -0
  149. oats/session/caveman.py.AGENT.python.tools.json +7 -0
  150. oats/session/compaction.py +463 -0
  151. oats/session/compaction.py.AGENT.python.tools.json +7 -0
  152. oats/session/debug_trace.py +41 -0
  153. oats/session/debug_trace.py.AGENT.python.tools.json +7 -0
  154. oats/session/file_cache.py +108 -0
  155. oats/session/file_cache.py.AGENT.python.tools.json +7 -0
  156. oats/session/message.py +214 -0
  157. oats/session/message.py.AGENT.python.tools.json +7 -0
  158. oats/session/metrics.py +52 -0
  159. oats/session/metrics.py.AGENT.python.tools.json +7 -0
  160. oats/session/models.py +43 -0
  161. oats/session/models.py.AGENT.python.tools.json +5 -0
  162. oats/session/modes.py +107 -0
  163. oats/session/modes.py.AGENT.python.tools.json +7 -0
  164. oats/session/processor.py +1600 -0
  165. oats/session/processor.py.AGENT.python.tools.json +7 -0
  166. oats/session/screenshot_store.py +157 -0
  167. oats/session/screenshot_store.py.AGENT.python.tools.json +7 -0
  168. oats/session/session.py +224 -0
  169. oats/session/session.py.AGENT.python.tools.json +7 -0
  170. oats/session/skill_selector.py +156 -0
  171. oats/session/skill_selector.py.AGENT.python.tools.json +7 -0
  172. oats/session/task_budget.py +159 -0
  173. oats/session/task_budget.py.AGENT.python.tools.json +7 -0
  174. oats/session/token_budget.py +90 -0
  175. oats/session/token_budget.py.AGENT.python.tools.json +7 -0
  176. oats/session/tool_retention.py +80 -0
  177. oats/session/tool_retention.py.AGENT.python.tools.json +7 -0
  178. oats/session/usage.py +139 -0
  179. oats/session/usage.py.AGENT.python.tools.json +7 -0
  180. oats/tool/AGENT.dir.python.tools.json +1 -0
  181. oats/tool/AGENT.python.tools.md +299 -0
  182. oats/tool/agent_tool.py +447 -0
  183. oats/tool/agent_tool.py.AGENT.python.tools.json +7 -0
  184. oats/tool/aws_safety.py +189 -0
  185. oats/tool/aws_safety.py.AGENT.python.tools.json +7 -0
  186. oats/tool/bash.py +188 -0
  187. oats/tool/bash.py.AGENT.python.tools.json +7 -0
  188. oats/tool/edit.py +437 -0
  189. oats/tool/generate_readme.py +280 -0
  190. oats/tool/generate_readme.py.AGENT.python.tools.json +7 -0
  191. oats/tool/glob_tool.py +183 -0
  192. oats/tool/glob_tool.py.AGENT.python.tools.json +7 -0
  193. oats/tool/grep.py +337 -0
  194. oats/tool/grep.py.AGENT.python.tools.json +7 -0
  195. oats/tool/init_tools.py +152 -0
  196. oats/tool/init_tools.py.AGENT.python.tools.json +7 -0
  197. oats/tool/lsp_tool.py +315 -0
  198. oats/tool/lsp_tool.py.AGENT.python.tools.json +7 -0
  199. oats/tool/memory_tool.py +241 -0
  200. oats/tool/memory_tool.py.AGENT.python.tools.json +7 -0
  201. oats/tool/multiedit.py +198 -0
  202. oats/tool/multiedit.py.AGENT.python.tools.json +7 -0
  203. oats/tool/patch.py +343 -0
  204. oats/tool/patch.py.AGENT.python.tools.json +7 -0
  205. oats/tool/plan.py +318 -0
  206. oats/tool/plan.py.AGENT.python.tools.json +7 -0
  207. oats/tool/playwright_search.py +227 -0
  208. oats/tool/playwright_search.py.AGENT.python.tools.json +7 -0
  209. oats/tool/question.py +245 -0
  210. oats/tool/question.py.AGENT.python.tools.json +7 -0
  211. oats/tool/read.py +199 -0
  212. oats/tool/read.py.AGENT.python.tools.json +7 -0
  213. oats/tool/registry.py +184 -0
  214. oats/tool/registry.py.AGENT.python.tools.json +7 -0
  215. oats/tool/todowrite.py +224 -0
  216. oats/tool/todowrite.py.AGENT.python.tools.json +7 -0
  217. oats/tool/tool_search.py +176 -0
  218. oats/tool/tool_search.py.AGENT.python.tools.json +7 -0
  219. oats/tool/webfetch.py +200 -0
  220. oats/tool/webfetch.py.AGENT.python.tools.json +7 -0
  221. oats/tool/websearch.py +277 -0
  222. oats/tool/websearch.py.AGENT.python.tools.json +7 -0
  223. oats/tool/write.py +154 -0
  224. oats/tool/write.py.AGENT.python.tools.json +7 -0
  225. oats/trajectory/AGENT.dir.python.tools.json +1 -0
  226. oats/trajectory/AGENT.python.tools.md +61 -0
  227. oats/trajectory/__init__.py +17 -0
  228. oats/trajectory/logger.py +119 -0
  229. oats/trajectory/logger.py.AGENT.python.tools.json +7 -0
  230. oats/trajectory/metrics.py +222 -0
  231. oats/trajectory/metrics.py.AGENT.python.tools.json +7 -0
  232. oats/trajectory/report.py +37 -0
  233. oats/trajectory/report.py.AGENT.python.tools.json +7 -0
  234. oats/trajectory/retrieval.py +140 -0
  235. oats/trajectory/retrieval.py.AGENT.python.tools.json +7 -0
  236. oats/trajectory/store.py +366 -0
  237. oats/trajectory/store.py.AGENT.python.tools.json +7 -0
  238. oats_coder-1.0.2.dist-info/METADATA +271 -0
  239. oats_coder-1.0.2.dist-info/RECORD +242 -0
  240. oats_coder-1.0.2.dist-info/WHEEL +4 -0
  241. oats_coder-1.0.2.dist-info/entry_points.txt +4 -0
  242. oats_coder-1.0.2.dist-info/licenses/LICENSE +1 -0
oats/agent/agent.py ADDED
@@ -0,0 +1,176 @@
1
+ """
2
+ Agent definitions and management.
3
+ """
4
+ from __future__ import annotations
5
+
6
+
7
+ from dataclasses import dataclass, field
8
+ from enum import Enum
9
+ from typing import Any
10
+
11
+
12
+ class AgentType(str, Enum):
13
+ """Types of sub-agents with different tool access levels."""
14
+
15
+ GENERAL = "general"
16
+ EXPLORE = "explore"
17
+ PLAN = "plan"
18
+ VERIFY = "verify"
19
+
20
+
21
+ # Per-type tool restrictions. None means all tools allowed.
22
+ AGENT_TYPE_TOOLS: dict[AgentType, set[str] | None] = {
23
+ AgentType.GENERAL: None, # all tools
24
+ AgentType.EXPLORE: {
25
+ "read", "glob", "grep", "bash", "webfetch", "websearch",
26
+ "question", "todowrite", "todoread",
27
+ },
28
+ AgentType.PLAN: {
29
+ "read", "glob", "grep", "question",
30
+ "plan_enter", "plan_exit", "plan_status",
31
+ "todowrite", "todoread", "webfetch", "websearch",
32
+ },
33
+ AgentType.VERIFY: {
34
+ "read", "glob", "grep", "bash", "question",
35
+ "todowrite", "todoread",
36
+ },
37
+ }
38
+
39
+ # Max iterations per agent type
40
+ AGENT_TYPE_MAX_ITERATIONS: dict[AgentType, int] = {
41
+ AgentType.GENERAL: 200,
42
+ AgentType.EXPLORE: 100,
43
+ AgentType.PLAN: 100,
44
+ AgentType.VERIFY: 100,
45
+ }
46
+
47
+
48
+ @dataclass
49
+ class Agent:
50
+ """Definition of an AI agent."""
51
+
52
+ name: str
53
+ description: str = ""
54
+ prompt: str = ""
55
+ agent_type: AgentType = AgentType.GENERAL
56
+ model_id: str | None = None
57
+ provider_id: str | None = None
58
+ temperature: float | None = None
59
+ top_p: float | None = None
60
+ tools: list[str] = field(default_factory=list) # Tool names to enable
61
+ allowed_tools: set[str] | None = None # Restrict tool access
62
+ max_iterations: int = 200
63
+ options: dict[str, Any] = field(default_factory=dict)
64
+
65
+
66
+ # Built-in agents
67
+ BUILTIN_AGENTS: list[Agent] = [
68
+ Agent(
69
+ name="default",
70
+ description="General-purpose coding assistant",
71
+ prompt="You are a helpful AI coding assistant.",
72
+ agent_type=AgentType.GENERAL,
73
+ ),
74
+ Agent(
75
+ name="coder",
76
+ description="Focused on writing and modifying code",
77
+ prompt="""You are an expert programmer. Focus on:
78
+ - Writing clean, efficient code
79
+ - Following best practices
80
+ - Using appropriate design patterns
81
+ - Adding helpful comments when needed""",
82
+ agent_type=AgentType.GENERAL,
83
+ ),
84
+ Agent(
85
+ name="explorer",
86
+ description="Read-only codebase exploration specialist",
87
+ prompt="""You are a codebase exploration specialist. Focus on:
88
+ - Reading and understanding code structure
89
+ - Finding files, classes, and functions
90
+ - Answering questions about the codebase
91
+ - Do NOT modify any files""",
92
+ agent_type=AgentType.EXPLORE,
93
+ allowed_tools=AGENT_TYPE_TOOLS[AgentType.EXPLORE],
94
+ max_iterations=100,
95
+ ),
96
+ Agent(
97
+ name="planner",
98
+ description="Plans implementation approaches",
99
+ prompt="""You are a software architect. Focus on:
100
+ - Designing implementation approaches
101
+ - Identifying files that need changes
102
+ - Considering trade-offs between approaches
103
+ - Creating step-by-step plans""",
104
+ agent_type=AgentType.PLAN,
105
+ allowed_tools=AGENT_TYPE_TOOLS[AgentType.PLAN],
106
+ max_iterations=100,
107
+ ),
108
+ Agent(
109
+ name="reviewer",
110
+ description="Code review specialist",
111
+ prompt="""You are a code review expert. Focus on:
112
+ - Finding bugs and issues
113
+ - Suggesting improvements
114
+ - Checking for security vulnerabilities
115
+ - Ensuring code quality""",
116
+ agent_type=AgentType.VERIFY,
117
+ allowed_tools=AGENT_TYPE_TOOLS[AgentType.VERIFY],
118
+ max_iterations=100,
119
+ ),
120
+ Agent(
121
+ name="explainer",
122
+ description="Explains code and concepts",
123
+ prompt="""You are a technical educator. Focus on:
124
+ - Clear, simple explanations
125
+ - Breaking down complex concepts
126
+ - Providing examples
127
+ - Answering follow-up questions""",
128
+ agent_type=AgentType.EXPLORE,
129
+ allowed_tools=AGENT_TYPE_TOOLS[AgentType.EXPLORE],
130
+ max_iterations=100,
131
+ ),
132
+ ]
133
+
134
+
135
+ class AgentRegistry:
136
+ """Registry of available agents."""
137
+
138
+ def __init__(self) -> None:
139
+ self._agents: dict[str, Agent] = {}
140
+ # Load built-in agents
141
+ for agent in BUILTIN_AGENTS:
142
+ self.register(agent)
143
+
144
+ def register(self, agent: Agent) -> None:
145
+ """Register an agent."""
146
+ self._agents[agent.name] = agent
147
+
148
+ def get(self, name: str) -> Agent | None:
149
+ """Get an agent by name."""
150
+ return self._agents.get(name)
151
+
152
+ def list(self) -> list[Agent]:
153
+ """List all agents."""
154
+ return list(self._agents.values())
155
+
156
+
157
+ # Global agent registry
158
+ _registry: AgentRegistry | None = None
159
+
160
+
161
+ def get_agent_registry() -> AgentRegistry:
162
+ """Get the global agent registry."""
163
+ global _registry
164
+ if _registry is None:
165
+ _registry = AgentRegistry()
166
+ return _registry
167
+
168
+
169
+ def get_agent(name: str) -> Agent | None:
170
+ """Get an agent by name."""
171
+ return get_agent_registry().get(name)
172
+
173
+
174
+ def list_agents() -> list[Agent]:
175
+ """List all available agents."""
176
+ return get_agent_registry().list()
@@ -0,0 +1,7 @@
1
+ {
2
+ "create_custom_agent": "create a new Agent dataclass instance with a custom name, prompt, and agent_type",
3
+ "register_agent": "register a custom Agent into the AgentRegistry so it can be retrieved by name",
4
+ "get_agent": "get an Agent by name from the global registry using the get_agent function",
5
+ "list_agents": "list all available agents from the global registry using the list_agents function",
6
+ "get_agent_registry": "get the global AgentRegistry singleton instance using the get_agent_registry function"
7
+ }
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import argparse
4
+
5
+ from oats.get_oat_config import get_oat_config
6
+ from oats.models import OatPromptChoices
7
+ from oats.log import gl
8
+
9
+ log = gl('oat.get_choices')
10
+
11
+ def agent_get_tool_choices_for_prompt(prompt: str, top_k: int = 5, verbose: bool = False) -> OatPromptChoices:
12
+ oat_config = get_oat_config()
13
+ # multi-tool resolution depending on need/use case
14
+ choices = oat_config.get_prompt_choices(prompt=prompt, verbose=verbose)
15
+ if not choices.status:
16
+ choices = oat_config.get_best_matches_bm25(prompt=prompt, top_k=top_k, verbose=verbose)
17
+ return choices
18
+
19
+ def parse_args(args=None) -> argparse.Namespace:
20
+ parser = argparse.ArgumentParser(description='Get prompt choices from the OAT index.')
21
+ parser.add_argument('-p', '--prompt', type=str, required=True, help='The prompt text to extract choices for.')
22
+ parser.add_argument('-t', '--top-k', type=int, default=5, help='Number of top results to return when using BM25 (default: 5).')
23
+ parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose logging which will break jq command line piping for debugging')
24
+ return parser.parse_args(args)
25
+
26
+ def main(args=None):
27
+ args = parse_args(args)
28
+ result = agent_get_tool_choices_for_prompt(args.prompt, top_k=args.top_k, verbose=args.verbose)
29
+ print(result.model_dump_json(indent=2))
30
+
31
+ if __name__ == '__main__':
32
+ main()
@@ -0,0 +1,7 @@
1
+ {
2
+ "get_tool_choices_for_prompt": "get tool choices for a prompt using the OAT index with multi-tool resolution",
3
+ "get_prompt_choices": "get prompt choices from the OAT config using the get_prompt_choices method",
4
+ "get_best_matches_bm25": "get the best BM25 matches for a prompt when prompt choices are unavailable",
5
+ "run_agent_get_tool_choices_cli": "run the CLI tool to get prompt choices from the OAT index with a prompt argument",
6
+ "parse_args": "parse command line arguments for the OAT prompt choices CLI including prompt top-k and verbose"
7
+ }
@@ -0,0 +1,430 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Call tool with loader — dynamically load tools from source and wrap them as LocalTool instances.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import os
8
+ import sys
9
+ import traceback
10
+ import argparse
11
+ import asyncio
12
+ from pathlib import Path
13
+ from typing import Any, Tuple
14
+ import ujson as json
15
+ from oats.load_tools_from_source1 import get_best_tools_for_prompt
16
+ from oats.tool.registry import Tool, ToolContext, ToolResult
17
+ from oats.pp import pp
18
+ from oats.log import gl
19
+
20
+ log = gl(__name__)
21
+
22
+ DEFAULT_PROMPT = "get utc"
23
+
24
+ # ---------------------------------------------------------------------------
25
+ # LocalTool — a mutable Tool subclass backed by a real callable
26
+ # ---------------------------------------------------------------------------
27
+
28
+ class LocalTool(Tool):
29
+ """A Tool whose attributes are set via setters rather than hard-coded properties."""
30
+
31
+ def __init__(self):
32
+ self._name: str = ""
33
+ self._aliases: list[str] = []
34
+ self._keywords: list[str] = []
35
+ self._always_load: bool = False
36
+ self._strict: bool = False
37
+ self._description: str = ""
38
+ self._parameters: dict[str, Any] = {}
39
+ self._requires_permission_msg: str | None = None
40
+ self._output: str = ""
41
+ self.tool_context: ToolContext | None = None
42
+ self._impl: Any = None # the underlying callable
43
+
44
+ # -- setters ----------------------------------------------------------
45
+
46
+ def set_name(self, value: str) -> None:
47
+ self._name = value
48
+
49
+ def set_aliases(self, value: list[str]) -> None:
50
+ self._aliases = value
51
+
52
+ def set_keywords(self, value: list[str]) -> None:
53
+ self._keywords = value
54
+
55
+ def set_always_load(self, value: bool) -> None:
56
+ self._always_load = value
57
+
58
+ def set_strict(self, value: bool) -> None:
59
+ self._strict = value
60
+
61
+ def set_description(self, value: str) -> None:
62
+ self._description = value
63
+
64
+ def set_parameters(self, value: dict[str, Any]) -> None:
65
+ self._parameters = value
66
+
67
+ def set_requires_permission(self, value: str | None) -> None:
68
+ self._requires_permission_msg = value
69
+
70
+ def set_output(self, value: str) -> None:
71
+ self._output = value
72
+
73
+ def set_tool_context(self, value: ToolContext) -> None:
74
+ self.tool_context = value
75
+
76
+ def set_impl(self, impl: Any) -> None:
77
+ self._impl = impl
78
+
79
+ # -- abstract properties (required by Tool ABC) -----------------------
80
+
81
+ @property
82
+ def name(self) -> str:
83
+ return self._name
84
+
85
+ @property
86
+ def description(self) -> str:
87
+ return self._description
88
+
89
+ @property
90
+ def parameters(self) -> dict[str, Any]:
91
+ return self._parameters
92
+
93
+ # -- optional overrides -----------------------------------------------
94
+
95
+ @property
96
+ def aliases(self) -> list[str]:
97
+ return self._aliases
98
+
99
+ @property
100
+ def keywords(self) -> list[str]:
101
+ return self._keywords
102
+
103
+ @property
104
+ def always_load(self) -> bool:
105
+ return self._always_load
106
+
107
+ @property
108
+ def strict(self) -> bool:
109
+ return self._strict
110
+
111
+ @property
112
+ def output(self) -> str:
113
+ return self._output
114
+
115
+ def requires_permission(self, args: dict[str, Any], ctx: ToolContext) -> str | None:
116
+ return self._requires_permission_msg
117
+
118
+ async def execute(self, args: dict[str, Any], ctx: ToolContext) -> ToolResult:
119
+ """Execute the underlying callable and return a ToolResult."""
120
+ self.tool_context = ctx
121
+ try:
122
+ if self._impl is None:
123
+ return ToolResult(
124
+ title=self._name,
125
+ output="",
126
+ error=f"No implementation set for tool '{self._name}'",
127
+ )
128
+ result = self._impl(**args)
129
+ self._output = str(result)
130
+ return ToolResult(
131
+ title=self._name,
132
+ output=str(result),
133
+ )
134
+ except Exception as e:
135
+ return ToolResult(
136
+ title=f"{self._name} (error)",
137
+ output="",
138
+ error=str(e),
139
+ )
140
+
141
+
142
+ # ---------------------------------------------------------------------------
143
+ # Loader
144
+ # ---------------------------------------------------------------------------
145
+
146
+ def load_tools_from_repo_uses_index(prompt: str, file_path: str | None = None, min_score: float = 0.0, verbose: bool = False) -> Tuple[bool, list, dict, list, list[dict], dict, list, list]:
147
+ """
148
+ Import source code using get_best_tools_for_prompt(), then create a
149
+ LocalTool for each matched tool.
150
+
151
+ Args:
152
+ file_path: Path to the tool-uses index JSON file (or None for default).
153
+ prompt: The prompt to match tools against.
154
+
155
+ Returns:
156
+ A list of LocalTool instances ready for use.
157
+ """
158
+ if verbose:
159
+ log.info(f"load_tools_from_repo_uses_index: file_path={file_path!r}, prompt={prompt[0:2000]}")
160
+
161
+ local_tool_impls = []
162
+ local_tool_names = []
163
+ local_tools: list[LocalTool] = []
164
+ found_best_tool, all_tools, all_tool_impls, best_files, best_tools, best_impls = get_best_tools_for_prompt(prompt=prompt, min_score=min_score, tool_schema=file_path, verbose=verbose)
165
+
166
+ if verbose:
167
+ log.info(
168
+ f"found_best_tool={found_best_tool}, "
169
+ f"len(best_tools)={len(best_tools)}, "
170
+ f"len(best_impls)={len(best_impls)}, "
171
+ f"best_files={best_files}")
172
+
173
+ if not found_best_tool:
174
+ if verbose:
175
+ log.info(f"### Sorry!! No best tools found in file: {__file__} for prompt — returning empty list")
176
+ return found_best_tool, all_tools, all_tool_impls, best_files, best_tools, best_impls, local_tools, local_tool_names
177
+
178
+ for tool_schema in best_tools:
179
+ if "function" not in tool_schema:
180
+ continue
181
+ func_info = tool_schema["function"]
182
+ fname = func_info.get("name", "")
183
+ if not fname:
184
+ continue
185
+
186
+ impl = best_impls.get(fname)
187
+
188
+ lt = LocalTool()
189
+ lt.set_name(fname)
190
+ lt.set_description(func_info.get("description", ""))
191
+ lt.set_parameters(func_info.get("parameters", {}))
192
+ lt.set_aliases([])
193
+ lt.set_keywords([])
194
+ lt.set_always_load(True)
195
+ lt.set_strict(False)
196
+ lt.set_requires_permission(f"Execute tool: {fname}")
197
+ lt.set_output("")
198
+ if impl is not None:
199
+ lt.set_impl(impl)
200
+ if verbose:
201
+ log.info(f"Created LocalTool '{fname}' with impl")
202
+ else:
203
+ log.warning(f"Created LocalTool '{fname}' WITHOUT impl")
204
+
205
+ local_tool_names.append(fname)
206
+ local_tools.append(lt)
207
+
208
+ if verbose:
209
+ log.info(f"load_tools_from_repo_uses_index: created {len(local_tools)} LocalTool(s)")
210
+ log.debug('# Local Tools\n\n')
211
+ print(all_tools)
212
+ print(all_tool_impls)
213
+ print(best_files)
214
+ print(best_tools)
215
+ print(best_impls)
216
+ return found_best_tool, all_tools, all_tool_impls, best_files, best_tools, best_impls, local_tools, local_tool_names
217
+
218
+
219
+ def run_tool_call(
220
+ prompt: str,
221
+ tools: list,
222
+ tool_impls: dict,
223
+ provider_id: str | None = None,
224
+ api_base: str | None = None,
225
+ api_key: str | None = None,
226
+ model: str | None = None,
227
+ verbose: bool = False,
228
+ ) -> Tuple[bool, str]:
229
+
230
+ if model is None:
231
+ model = 'openai/google/functiongemma-270m-it'
232
+ if api_base is None:
233
+ api_base = 'http://0.0.0.0:20700/v1'
234
+ if prompt == '{}':
235
+ raise Exception(f'# Sorry!! call_tool_with_loader1.run_tool_call requires a valid prompt: {prompt}')
236
+ messages = [{"role": "user", "content": prompt}]
237
+ api_key = os.getenv('TOOL_API_KEY', 'CHANGE_PASSWORD')
238
+ call_kwargs = dict(
239
+ model=model,
240
+ messages=messages,
241
+ tools=tools,
242
+ tool_choice="auto",
243
+ api_base=api_base,
244
+ api_key=api_key,
245
+ )
246
+
247
+ if verbose:
248
+ log.debug('-------\nTools - Start\n-------')
249
+ for tool_name in tools:
250
+ print(tool_name)
251
+ log.debug('-------\nTools - End\n-------')
252
+ log.debug('-------\nTools Impls\n-------')
253
+ num_tool_impls = len(tool_impls)
254
+ for tidx, tool_name in enumerate(tool_impls):
255
+ tool_data = tool_impls[tool_name]
256
+ log.info(f'## Tool {tidx + 1}/{num_tool_impls}\nname: **{tool_name}**\nimplementation:\n```\n{tool_data}\n```\n')
257
+ print('---')
258
+ log.debug('-------\nTools Impls - End\n-------')
259
+
260
+ # print(f"\n{'='*80}")
261
+ # print(f"Prompt : {prompt}")
262
+ # print(f"Model : {model}")
263
+ # print(f"API : {api_base}")
264
+ # print(f"Tools : {[t['function']['name'] for t in tools]}")
265
+ # print(f"{'='*80}\n")
266
+
267
+ if verbose:
268
+ log.info(f'loading litellm')
269
+ import litellm
270
+ if verbose:
271
+ log.info(f'### First turn calling tool with args:\n\n```\n{pp(call_kwargs)}\n```\n')
272
+ resp = litellm.completion(**call_kwargs)
273
+ msg = resp.choices[0].message
274
+ messages.append(msg.model_dump(exclude_none=True))
275
+ if not msg.tool_calls:
276
+ err = f'### Sorry!! {__file__} - no_tool_calls_call_tool_with_loader1 detected. model response:\n```\n{msg.content}\n```\n'
277
+ log.error(err)
278
+ print(err)
279
+ log.error('call_kwargs')
280
+ print(pp(call_kwargs))
281
+ log.error('msg')
282
+ print(msg)
283
+ return False, err
284
+
285
+ # print(f"Tool calls:\n{pp([tc.model_dump() for tc in msg.tool_calls])}\n")
286
+
287
+ for tc in msg.tool_calls:
288
+ fname = str(tc.function.name)
289
+ try:
290
+ fargs = json.loads(tc.function.arguments or "{}")
291
+ except Exception:
292
+ log.error(f"### Sorry failed to json.loads Tool '{fname}' raised:\n```\n{traceback.format_exc()}\n```\n")
293
+ fargs = {}
294
+ if fname not in tool_impls:
295
+ result = f"Error: tool '{fname}' not found"
296
+ log.error(result)
297
+ else:
298
+ try:
299
+ result = tool_impls[fname](**fargs)
300
+ if verbose:
301
+ log.info(f"Tool '{fname}' → {result}")
302
+ except Exception as exc:
303
+ result = f"### Sorry!! Error:\n```\n{traceback.format_exc()}\n```\n"
304
+ log.error(f"### Sorry failed to json.loads Tool '{fname}' raised:\n```\n{traceback.format_exc()}\n```\n")
305
+
306
+ messages.append({
307
+ "role": "tool",
308
+ "tool_call_id": tc.id,
309
+ "name": fname,
310
+ "content": str(result),
311
+ })
312
+
313
+ # print(f"Tool results:\n{pp(messages[-len(msg.tool_calls):])}\n")
314
+
315
+ call_kwargs["messages"] = messages
316
+ if verbose:
317
+ log.info("Second turn ...")
318
+ # print(pp(call_kwargs))
319
+ resp2 = litellm.completion(**call_kwargs)
320
+ if resp2 is None:
321
+ err = f'### Sorry!! Failed second_tool_call_turn with error:\n```\n{resp2}\n```\n'
322
+ log.error(err)
323
+ return False, err
324
+ if not hasattr(resp2, 'choices'):
325
+ err = f'### Sorry!! Failed second_tool_call_turn with error_missing_choices:\n```\n{resp2}\n```\n'
326
+ log.error(err)
327
+ return False, err
328
+ answer = str(resp2.choices[0].message.content)
329
+ # messages.append({"role": "assistant", "content": answer})
330
+
331
+ if verbose:
332
+ log.info(f"run_tool_call_answers: {answer}\n")
333
+ # print(f"\n{'='*80}")
334
+ # print("Full conversation:")
335
+ # print(f"{'='*80}\n")
336
+ # print(pp(messages))
337
+ return True, answer
338
+
339
+ # ---------------------------------------------------------------------------
340
+ # Entry point
341
+ # ---------------------------------------------------------------------------
342
+
343
+ def main():
344
+ api_base = os.getenv("TOOL_FUNCTION_1", "http://0.0.0.0:20700/v1")
345
+
346
+ parser = argparse.ArgumentParser(
347
+ description="Load tools from source index and wrap them as LocalTool instances"
348
+ )
349
+ parser.add_argument(
350
+ "-p", "--prompt",
351
+ default=DEFAULT_PROMPT,
352
+ help=f"Prompt to match tools against (default: {DEFAULT_PROMPT})",
353
+ )
354
+ parser.add_argument(
355
+ "-s", "--schema",
356
+ default=None,
357
+ help="Path to JSON tool-uses index file (default: $CODER_TOOL_USES_INDEX)",
358
+ )
359
+ parser.add_argument(
360
+ "-t", "--top-k",
361
+ type=int,
362
+ default=5,
363
+ help="Number of top candidate files (default: 5)",
364
+ )
365
+ parser.add_argument(
366
+ "-m", "--min-score",
367
+ type=float,
368
+ default=0.0,
369
+ help="Minimum retrieval score threshold (default: 0.0)",
370
+ )
371
+ parser.add_argument(
372
+ "-r", "--rerank",
373
+ action="store_true",
374
+ help="Apply cross-encoder reranker after BM25 retrieval",
375
+ )
376
+ parser.add_argument(
377
+ "--rerank-model",
378
+ default="cross-encoder/ms-marco-MiniLM-L-6-v2",
379
+ help="Cross-encoder model for reranking",
380
+ )
381
+ parser.add_argument(
382
+ "--bm25-model",
383
+ default="bm25",
384
+ help="Retrieval model: bm25 | tfidf | <sentence-transformer> (default: bm25)",
385
+ )
386
+ args = parser.parse_args()
387
+
388
+ log.info(f"prompt={args.prompt!r}, schema={args.schema!r}, top_k={args.top_k}")
389
+
390
+ found_best_tool, all_tools, all_tool_impls, best_files, best_tools, best_impls, local_tools, local_tool_names = load_tools_from_repo_uses_index(file_path=args.schema, prompt=args.prompt)
391
+
392
+ if not found_best_tool:
393
+ log.error("No LocalTools were created — check your prompt and schema.")
394
+ sys.exit(1)
395
+
396
+ # Print summary
397
+ print(f"\n{'='*80}")
398
+ print(f"Loaded {len(all_tools)} LocalTool(s) for prompt: {args.prompt!r}")
399
+ print(f"{'='*80}\n")
400
+
401
+ for lt in local_tools:
402
+ print(f" Tool: {lt.name}")
403
+ print(f" description : {lt.description[:100]}")
404
+ print(f" parameters : {lt.parameters}")
405
+ print(f" aliases : {lt.aliases}")
406
+ print(f" keywords : {lt.keywords}")
407
+ print(f" always_load : {lt.always_load}")
408
+ print(f" strict : {lt.strict}")
409
+ print()
410
+
411
+ # Demo: execute the first tool with empty args
412
+ if local_tools:
413
+ ctx = ToolContext(
414
+ session_id="test-session",
415
+ project_dir=Path("./.coder"),
416
+ working_dir=Path("./.coder"),
417
+ )
418
+ lt = local_tools[0]
419
+ log.info(f"Executing LocalTool '{lt.name}' with args={{}}")
420
+ result = asyncio.run(lt.execute({}, ctx))
421
+ print(f"Execution result for '{lt.name}':")
422
+ print(f" title : {result.title}")
423
+ print(f" output : {result.output}")
424
+ print(f" error : {result.error}")
425
+
426
+ print("\ndone")
427
+
428
+
429
+ if __name__ == "__main__":
430
+ main()
@@ -0,0 +1,7 @@
1
+ {
2
+ "create_LocalTool": "create a LocalTool instance and set its name, description, parameters, and implementation callable",
3
+ "run_LocalTool_execute": "run the LocalTool execute method with args and a ToolContext to get a ToolResult",
4
+ "load_tools_from_repo_uses_index": "load tools from a source index file matched against a prompt and wrap them as LocalTool instances",
5
+ "run_tool_call": "run an LLM tool-calling loop using LiteLLM with dynamically loaded tool implementations and a user prompt",
6
+ "run_main_cli": "run the CLI to load tools from a repo index by prompt and execute the first matched tool"
7
+ }