codebuff-agent 2.1.74__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 (217) hide show
  1. codebuff_agent-2.1.74.data/data/etc/jupyter/jupyter_server_config.d/codebuff_agent.json +7 -0
  2. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/package.json +157 -0
  3. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/104.2b8f20f9d1ac2c9db01b.js +1 -0
  4. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/150.39fa98117cc8ca590465.js +1 -0
  5. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/166.386ff9b2c37ac865fdee.js +1 -0
  6. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/216.25cf81131bf7274dabf4.js +1 -0
  7. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/353.581bd1cb00746399b62e.js +1 -0
  8. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/367.575f9621b63c9a641aa9.js +1 -0
  9. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/421.11dac223a2c2da1b09f5.js +1 -0
  10. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/432.8513b4c6cc1d1232d165.js +2 -0
  11. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/432.8513b4c6cc1d1232d165.js.LICENSE.txt +19 -0
  12. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/439.ba5b5ef70ec6de214b8c.js +2 -0
  13. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/439.ba5b5ef70ec6de214b8c.js.LICENSE.txt +8 -0
  14. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/481.cc2dec9fa869d66f1407.js +1 -0
  15. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/487.0796e931d6e651482c14.js +1 -0
  16. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/49.d326f554b6f9ccde6580.js +1 -0
  17. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/541.26897ea32845659e36be.js +1 -0
  18. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/579.e569d3edd8391fd8fbb3.js +1 -0
  19. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/58.eb7cc679f7535cc8a2f1.js +2 -0
  20. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/58.eb7cc679f7535cc8a2f1.js.LICENSE.txt +8 -0
  21. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/666.d9bfdb23f98489d0a188.js +1 -0
  22. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/677.d266c39dbed33bc388b3.js +2 -0
  23. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/677.d266c39dbed33bc388b3.js.LICENSE.txt +8 -0
  24. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/68.26b6f59c8454adb17f20.js +204 -0
  25. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/694.4ac443814260057e68f7.js +1 -0
  26. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/791.61affb8d3480d3d60e0d.js +1 -0
  27. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/848.4d2db09cfbb09374b299.js +2 -0
  28. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/848.4d2db09cfbb09374b299.js.LICENSE.txt +9 -0
  29. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/868.5cfe7e1a348ec5af1b8c.js +1 -0
  30. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/97.ea559a5b8a9a09f17c6b.js +1 -0
  31. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/remoteEntry.5292fc9c6df7e7920cc8.js +1 -0
  32. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/style.js +4 -0
  33. codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/third-party-licenses.json +724 -0
  34. codebuff_agent-2.1.74.dist-info/METADATA +142 -0
  35. codebuff_agent-2.1.74.dist-info/RECORD +217 -0
  36. codebuff_agent-2.1.74.dist-info/WHEEL +4 -0
  37. codebuff_agent-2.1.74.dist-info/licenses/LICENSE +21 -0
  38. codebuff_core/__init__.py +153 -0
  39. codebuff_core/codebuff_config_defaults.json +64 -0
  40. codebuff_core/core/__init__.py +35 -0
  41. codebuff_core/core/code_validator.py +1238 -0
  42. codebuff_core/core/error_classifier.py +579 -0
  43. codebuff_core/core/exceptions.py +49 -0
  44. codebuff_core/data/tiktoken_cache/9b5ad71b2ce5302211f9c61530b329a4922fc6a4 +100256 -0
  45. codebuff_core/directory_policy.py +32 -0
  46. codebuff_core/factory.py +155 -0
  47. codebuff_core/interfaces.py +197 -0
  48. codebuff_core/knowledge/__init__.py +31 -0
  49. codebuff_core/knowledge/chunking.py +375 -0
  50. codebuff_core/knowledge/loader.py +339 -0
  51. codebuff_core/knowledge/watchdog_service.py +354 -0
  52. codebuff_core/legacy_paths.py +69 -0
  53. codebuff_core/llm/__init__.py +13 -0
  54. codebuff_core/llm/service.py +407 -0
  55. codebuff_core/managers/__init__.py +22 -0
  56. codebuff_core/managers/config_manager.py +690 -0
  57. codebuff_core/managers/remote_config.py +673 -0
  58. codebuff_core/managers/remote_skills.py +539 -0
  59. codebuff_core/managers/session_manager.py +255 -0
  60. codebuff_core/middleware/__init__.py +3 -0
  61. codebuff_core/middleware/error_recovery.py +394 -0
  62. codebuff_core/middleware/logging_utils.py +33 -0
  63. codebuff_core/models/__init__.py +111 -0
  64. codebuff_core/models/agent.py +306 -0
  65. codebuff_core/models/chat.py +57 -0
  66. codebuff_core/models/common.py +82 -0
  67. codebuff_core/models/rag.py +300 -0
  68. codebuff_core/platform_rules.py +127 -0
  69. codebuff_core/prompts/__init__.py +44 -0
  70. codebuff_core/prompts/auto_agent_prompts.py +1278 -0
  71. codebuff_core/services/__init__.py +30 -0
  72. codebuff_core/services/agent_service.py +345 -0
  73. codebuff_core/services/analytics_aggregate.py +837 -0
  74. codebuff_core/services/chat_service.py +898 -0
  75. codebuff_core/services/context_condenser.py +306 -0
  76. codebuff_core/services/pii_guardrail.py +393 -0
  77. codebuff_core/services/rag_chat_agent.py +2993 -0
  78. codebuff_core/services/rag_collections.py +2740 -0
  79. codebuff_core/services/rag_manager.py +890 -0
  80. codebuff_core/services/rag_service.py +145 -0
  81. codebuff_core/services/retriever.py +260 -0
  82. codebuff_core/services/vllm_embedding_service.py +290 -0
  83. codebuff_core/tests/__init__.py +1 -0
  84. codebuff_core/tests/conftest.py +81 -0
  85. codebuff_core/tests/test_error_recovery.py +647 -0
  86. codebuff_core/tests/test_factory.py +135 -0
  87. codebuff_core/tests/test_services.py +150 -0
  88. jupyter_ext/__init__.py +266 -0
  89. jupyter_ext/_version.py +4 -0
  90. jupyter_ext/agent/__init__.py +49 -0
  91. jupyter_ext/agent/agent_factory.py +2270 -0
  92. jupyter_ext/agent/ast_tools.py +451 -0
  93. jupyter_ext/agent/autofix_middleware.py +481 -0
  94. jupyter_ext/agent/background_procs.py +372 -0
  95. jupyter_ext/agent/code_history_middleware.py +211 -0
  96. jupyter_ext/agent/codex_autofix.py +160 -0
  97. jupyter_ext/agent/codex_bridge.py +3339 -0
  98. jupyter_ext/agent/codex_hooks.py +320 -0
  99. jupyter_ext/agent/codex_slash.py +685 -0
  100. jupyter_ext/agent/codex_threads.py +114 -0
  101. jupyter_ext/agent/content_injection.py +377 -0
  102. jupyter_ext/agent/continuation_middleware.py +548 -0
  103. jupyter_ext/agent/custom_skills.py +335 -0
  104. jupyter_ext/agent/file_diffs.py +99 -0
  105. jupyter_ext/agent/git_history.py +188 -0
  106. jupyter_ext/agent/guardrail_middleware.py +421 -0
  107. jupyter_ext/agent/json_repair_utils.py +54 -0
  108. jupyter_ext/agent/langchain_tools.py +2673 -0
  109. jupyter_ext/agent/mermaid_fixer.py +289 -0
  110. jupyter_ext/agent/mermaid_middleware.py +135 -0
  111. jupyter_ext/agent/middleware.py +359 -0
  112. jupyter_ext/agent/middleware_prompts.py +53 -0
  113. jupyter_ext/agent/observability.py +334 -0
  114. jupyter_ext/agent/patch_tool_calls.py +57 -0
  115. jupyter_ext/agent/prompts.py +112 -0
  116. jupyter_ext/agent/skill_middleware.py +257 -0
  117. jupyter_ext/agent/skills/airflow_dag_development.md +36 -0
  118. jupyter_ext/agent/skills/anomaly_detection.md +12 -0
  119. jupyter_ext/agent/skills/athena_sql.md +156 -0
  120. jupyter_ext/agent/skills/bdp_table_search.md +33 -0
  121. jupyter_ext/agent/skills/bigquery.md +65 -0
  122. jupyter_ext/agent/skills/bigquery_oracle.md +231 -0
  123. jupyter_ext/agent/skills/bigquery_tuning.md +307 -0
  124. jupyter_ext/agent/skills/classification_evaluation.md +11 -0
  125. jupyter_ext/agent/skills/clustering_analysis.md +12 -0
  126. jupyter_ext/agent/skills/comprehensive_eda.md +11 -0
  127. jupyter_ext/agent/skills/data_analysis.md +236 -0
  128. jupyter_ext/agent/skills/data_cleaning_pipeline.md +12 -0
  129. jupyter_ext/agent/skills/data_loading.md +158 -0
  130. jupyter_ext/agent/skills/data_quality_report.md +13 -0
  131. jupyter_ext/agent/skills/dimensionality_reduction.md +12 -0
  132. jupyter_ext/agent/skills/emr_script_development.md +17 -0
  133. jupyter_ext/agent/skills/experiment_tracking.md +13 -0
  134. jupyter_ext/agent/skills/feature_importance_analysis.md +10 -0
  135. jupyter_ext/agent/skills/hyperparameter_tuning.md +12 -0
  136. jupyter_ext/agent/skills/hypothesis_testing.md +12 -0
  137. jupyter_ext/agent/skills/imbalanced_data_handling.md +12 -0
  138. jupyter_ext/agent/skills/inference.md +392 -0
  139. jupyter_ext/agent/skills/lambda_function_development.md +19 -0
  140. jupyter_ext/agent/skills/model_comparison.md +11 -0
  141. jupyter_ext/agent/skills/model_explainability.md +12 -0
  142. jupyter_ext/agent/skills/model_training.md +318 -0
  143. jupyter_ext/agent/skills/pyspark.md +352 -0
  144. jupyter_ext/agent/skills/python_code_quality.md +35 -0
  145. jupyter_ext/agent/skills/regression_evaluation.md +12 -0
  146. jupyter_ext/agent/skills/spring_java_development.md +35 -0
  147. jupyter_ext/agent/skills/text_preprocessing.md +12 -0
  148. jupyter_ext/agent/skills/time_series_analysis.md +13 -0
  149. jupyter_ext/agent/skills/unit_testing.md +203 -0
  150. jupyter_ext/agent/todo_prompts.py +57 -0
  151. jupyter_ext/agent/tool_report.py +45 -0
  152. jupyter_ext/analytics_ingest.py +599 -0
  153. jupyter_ext/etc/jupyter/jupyter_server_config.d/codebuff_agent.json +7 -0
  154. jupyter_ext/handlers/__init__.py +8 -0
  155. jupyter_ext/handlers/analytics_handler.py +170 -0
  156. jupyter_ext/handlers/benchmark_handler.py +794 -0
  157. jupyter_ext/handlers/eda_benchmark_handler.py +1328 -0
  158. jupyter_ext/handlers/rag_admin_handler.py +689 -0
  159. jupyter_ext/handlers/websocket.py +497 -0
  160. jupyter_ext/http_handlers.py +8744 -0
  161. jupyter_ext/idle_monitor.py +346 -0
  162. jupyter_ext/labextension/package.json +157 -0
  163. jupyter_ext/labextension/static/104.2b8f20f9d1ac2c9db01b.js +1 -0
  164. jupyter_ext/labextension/static/150.39fa98117cc8ca590465.js +1 -0
  165. jupyter_ext/labextension/static/166.386ff9b2c37ac865fdee.js +1 -0
  166. jupyter_ext/labextension/static/216.25cf81131bf7274dabf4.js +1 -0
  167. jupyter_ext/labextension/static/353.581bd1cb00746399b62e.js +1 -0
  168. jupyter_ext/labextension/static/367.575f9621b63c9a641aa9.js +1 -0
  169. jupyter_ext/labextension/static/421.11dac223a2c2da1b09f5.js +1 -0
  170. jupyter_ext/labextension/static/432.8513b4c6cc1d1232d165.js +2 -0
  171. jupyter_ext/labextension/static/432.8513b4c6cc1d1232d165.js.LICENSE.txt +19 -0
  172. jupyter_ext/labextension/static/439.ba5b5ef70ec6de214b8c.js +2 -0
  173. jupyter_ext/labextension/static/439.ba5b5ef70ec6de214b8c.js.LICENSE.txt +8 -0
  174. jupyter_ext/labextension/static/481.cc2dec9fa869d66f1407.js +1 -0
  175. jupyter_ext/labextension/static/487.0796e931d6e651482c14.js +1 -0
  176. jupyter_ext/labextension/static/49.d326f554b6f9ccde6580.js +1 -0
  177. jupyter_ext/labextension/static/541.26897ea32845659e36be.js +1 -0
  178. jupyter_ext/labextension/static/579.e569d3edd8391fd8fbb3.js +1 -0
  179. jupyter_ext/labextension/static/58.eb7cc679f7535cc8a2f1.js +2 -0
  180. jupyter_ext/labextension/static/58.eb7cc679f7535cc8a2f1.js.LICENSE.txt +8 -0
  181. jupyter_ext/labextension/static/666.d9bfdb23f98489d0a188.js +1 -0
  182. jupyter_ext/labextension/static/677.d266c39dbed33bc388b3.js +2 -0
  183. jupyter_ext/labextension/static/677.d266c39dbed33bc388b3.js.LICENSE.txt +8 -0
  184. jupyter_ext/labextension/static/68.26b6f59c8454adb17f20.js +204 -0
  185. jupyter_ext/labextension/static/694.4ac443814260057e68f7.js +1 -0
  186. jupyter_ext/labextension/static/791.61affb8d3480d3d60e0d.js +1 -0
  187. jupyter_ext/labextension/static/848.4d2db09cfbb09374b299.js +2 -0
  188. jupyter_ext/labextension/static/848.4d2db09cfbb09374b299.js.LICENSE.txt +9 -0
  189. jupyter_ext/labextension/static/868.5cfe7e1a348ec5af1b8c.js +1 -0
  190. jupyter_ext/labextension/static/97.ea559a5b8a9a09f17c6b.js +1 -0
  191. jupyter_ext/labextension/static/remoteEntry.5292fc9c6df7e7920cc8.js +1 -0
  192. jupyter_ext/labextension/static/style.js +4 -0
  193. jupyter_ext/labextension/static/third-party-licenses.json +724 -0
  194. jupyter_ext/log_paths.py +113 -0
  195. jupyter_ext/resource_usage.py +519 -0
  196. jupyter_ext/secrets_bootstrap.py +116 -0
  197. jupyter_ext/team_access.py +105 -0
  198. jupyter_ext/tests/__init__.py +1 -0
  199. jupyter_ext/tests/test_context.py +234 -0
  200. jupyter_ext/tests/test_factory.py +563 -0
  201. jupyter_ext/tests/test_file_finder.py +139 -0
  202. jupyter_ext/tests/test_handlers.py +58 -0
  203. jupyter_ext/tests/test_mcp_selftest.py +204 -0
  204. jupyter_ext/tests/test_mermaid_fixer.py +272 -0
  205. jupyter_ext/tests/test_resource_tiers.py +298 -0
  206. jupyter_ext/tools/__init__.py +15 -0
  207. jupyter_ext/tools/base.py +90 -0
  208. jupyter_ext/tools/context.py +268 -0
  209. jupyter_ext/tools/dir_complete.py +86 -0
  210. jupyter_ext/tools/file_finder.py +303 -0
  211. jupyter_ext/tools/file_tools.py +244 -0
  212. jupyter_ext/tools/jupyter_tools.py +296 -0
  213. jupyter_ext/tools/registry.py +159 -0
  214. jupyter_ext/tools/search_tools.py +230 -0
  215. jupyter_ext/tools/security_tools.py +657 -0
  216. jupyter_ext/tools/shell_tools.py +109 -0
  217. jupyter_ext/tools/user_tools.py +132 -0
@@ -0,0 +1,296 @@
1
+ """Jupyter 노트북 도구."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional
6
+
7
+ from .base import BaseTool, ToolResult
8
+
9
+
10
+ class JupyterCellTool(BaseTool):
11
+ """Jupyter 셀 작업 도구."""
12
+
13
+ def __init__(self, kernel_manager: Any, contents_manager: Any):
14
+ self._kernel_manager = kernel_manager
15
+ self._contents_manager = contents_manager
16
+ self._notebook_path: Optional[str] = None
17
+ self._notebook_model: Optional[dict] = None
18
+
19
+ @property
20
+ def name(self) -> str:
21
+ return "jupyter_cell"
22
+
23
+ @property
24
+ def description(self) -> str:
25
+ return """Create, read, update, delete, or execute Jupyter notebook cells.
26
+ Operations:
27
+ - CREATE: Create a new code cell with the given code
28
+ - READ: Read the content of a cell at the given index
29
+ - UPDATE: Update the code of an existing cell
30
+ - DELETE: Delete a cell at the given index
31
+ - EXECUTE: Execute a cell and return the output"""
32
+
33
+ @property
34
+ def parameters(self) -> dict[str, Any]:
35
+ return {
36
+ "type": "object",
37
+ "properties": {
38
+ "operation": {
39
+ "type": "string",
40
+ "enum": ["CREATE", "READ", "UPDATE", "DELETE", "EXECUTE"],
41
+ "description": "The operation to perform",
42
+ },
43
+ "code": {
44
+ "type": "string",
45
+ "description": "The code content (for CREATE, UPDATE, EXECUTE)",
46
+ },
47
+ "cell_index": {
48
+ "type": "integer",
49
+ "description": "The cell index (for READ, UPDATE, DELETE, EXECUTE)",
50
+ },
51
+ "insert_after": {
52
+ "type": "integer",
53
+ "description": "Insert new cell after this index (for CREATE)",
54
+ },
55
+ },
56
+ "required": ["operation"],
57
+ }
58
+
59
+ @property
60
+ def risk_level(self) -> str:
61
+ return "medium"
62
+
63
+ def set_notebook(self, path: str, model: dict) -> None:
64
+ """현재 노트북 컨텍스트를 설정합니다."""
65
+ self._notebook_path = path
66
+ self._notebook_model = model
67
+
68
+ async def execute(self, **kwargs: Any) -> ToolResult:
69
+ """Jupyter 셀 작업을 실행합니다."""
70
+ operation = kwargs.get("operation", "").upper()
71
+
72
+ if operation == "CREATE":
73
+ return await self._create_cell(
74
+ kwargs.get("code", ""), kwargs.get("insert_after")
75
+ )
76
+ elif operation == "READ":
77
+ return await self._read_cell(kwargs.get("cell_index", 0))
78
+ elif operation == "UPDATE":
79
+ return await self._update_cell(
80
+ kwargs.get("cell_index", 0), kwargs.get("code", "")
81
+ )
82
+ elif operation == "DELETE":
83
+ return await self._delete_cell(kwargs.get("cell_index", 0))
84
+ elif operation == "EXECUTE":
85
+ return await self._execute_cell(
86
+ kwargs.get("cell_index"), kwargs.get("code")
87
+ )
88
+ else:
89
+ return ToolResult(success=False, error=f"알 수 없는 연산: {operation}")
90
+
91
+ async def _create_cell(
92
+ self, code: str, insert_after: Optional[int] = None
93
+ ) -> ToolResult:
94
+ """새 셀을 생성합니다."""
95
+ if not self._notebook_model:
96
+ return ToolResult(success=False, error="노트북 컨텍스트가 설정되지 않음")
97
+
98
+ cells = self._notebook_model.get("content", {}).get("cells", [])
99
+ new_cell = {
100
+ "cell_type": "code",
101
+ "source": code,
102
+ "metadata": {},
103
+ "outputs": [],
104
+ "execution_count": None,
105
+ }
106
+
107
+ if insert_after is not None and 0 <= insert_after < len(cells):
108
+ cells.insert(insert_after + 1, new_cell)
109
+ cell_index = insert_after + 1
110
+ else:
111
+ cells.append(new_cell)
112
+ cell_index = len(cells) - 1
113
+
114
+ return ToolResult(
115
+ success=True,
116
+ data={"cell_index": cell_index, "operation": "created"},
117
+ metadata={"cell_type": "code"},
118
+ )
119
+
120
+ async def _read_cell(self, cell_index: int) -> ToolResult:
121
+ """셀 내용을 읽습니다."""
122
+ if not self._notebook_model:
123
+ return ToolResult(success=False, error="노트북 컨텍스트가 설정되지 않음")
124
+
125
+ cells = self._notebook_model.get("content", {}).get("cells", [])
126
+ if not 0 <= cell_index < len(cells):
127
+ return ToolResult(success=False, error=f"잘못된 셀 인덱스: {cell_index}")
128
+
129
+ cell = cells[cell_index]
130
+ return ToolResult(
131
+ success=True,
132
+ data={
133
+ "source": cell.get("source", ""),
134
+ "cell_type": cell.get("cell_type", "code"),
135
+ "outputs": cell.get("outputs", []),
136
+ },
137
+ )
138
+
139
+ async def _update_cell(self, cell_index: int, code: str) -> ToolResult:
140
+ """셀 내용을 업데이트합니다."""
141
+ if not self._notebook_model:
142
+ return ToolResult(success=False, error="노트북 컨텍스트가 설정되지 않음")
143
+
144
+ cells = self._notebook_model.get("content", {}).get("cells", [])
145
+ if not 0 <= cell_index < len(cells):
146
+ return ToolResult(success=False, error=f"잘못된 셀 인덱스: {cell_index}")
147
+
148
+ cells[cell_index]["source"] = code
149
+ return ToolResult(
150
+ success=True,
151
+ data={"cell_index": cell_index, "operation": "updated"},
152
+ )
153
+
154
+ async def _delete_cell(self, cell_index: int) -> ToolResult:
155
+ """셀을 삭제합니다."""
156
+ if not self._notebook_model:
157
+ return ToolResult(success=False, error="노트북 컨텍스트가 설정되지 않음")
158
+
159
+ cells = self._notebook_model.get("content", {}).get("cells", [])
160
+ if not 0 <= cell_index < len(cells):
161
+ return ToolResult(success=False, error=f"잘못된 셀 인덱스: {cell_index}")
162
+
163
+ del cells[cell_index]
164
+ return ToolResult(
165
+ success=True,
166
+ data={"cell_index": cell_index, "operation": "deleted"},
167
+ )
168
+
169
+ async def _execute_cell(
170
+ self, cell_index: Optional[int], code: Optional[str]
171
+ ) -> ToolResult:
172
+ """셀 또는 코드를 실행합니다."""
173
+ # 코드가 직접 제공되면 실행
174
+ if code:
175
+ return await self._execute_code(code)
176
+
177
+ # 그렇지 않으면 주어진 인덱스의 셀 실행
178
+ if cell_index is not None and self._notebook_model:
179
+ cells = self._notebook_model.get("content", {}).get("cells", [])
180
+ if 0 <= cell_index < len(cells):
181
+ cell_code = cells[cell_index].get("source", "")
182
+ return await self._execute_code(cell_code)
183
+
184
+ return ToolResult(
185
+ success=False, error="코드 또는 유효한 셀 인덱스가 제공되지 않음"
186
+ )
187
+
188
+ async def _execute_code(self, code: str) -> ToolResult:
189
+ """커널에서 코드를 실행합니다."""
190
+ # 실제 커널과 통합됨
191
+ # 현재는 플레이스홀더 반환
192
+ return ToolResult(
193
+ success=True,
194
+ data={
195
+ "output": "코드 실행 플레이스홀더",
196
+ "execution_count": 1,
197
+ },
198
+ metadata={"executed": True},
199
+ )
200
+
201
+
202
+ class JupyterMarkdownTool(BaseTool):
203
+ """Jupyter 마크다운 셀 작업 도구."""
204
+
205
+ def __init__(self, contents_manager: Any):
206
+ self._contents_manager = contents_manager
207
+ self._notebook_model: Optional[dict] = None
208
+
209
+ @property
210
+ def name(self) -> str:
211
+ return "jupyter_markdown"
212
+
213
+ @property
214
+ def description(self) -> str:
215
+ return """Create or update markdown cells in the Jupyter notebook.
216
+ Use this for adding documentation, explanations, or headers."""
217
+
218
+ @property
219
+ def parameters(self) -> dict[str, Any]:
220
+ return {
221
+ "type": "object",
222
+ "properties": {
223
+ "operation": {
224
+ "type": "string",
225
+ "enum": ["CREATE", "UPDATE"],
226
+ "description": "The operation to perform",
227
+ },
228
+ "content": {
229
+ "type": "string",
230
+ "description": "The markdown content",
231
+ },
232
+ "cell_index": {
233
+ "type": "integer",
234
+ "description": "The cell index (for UPDATE)",
235
+ },
236
+ "insert_after": {
237
+ "type": "integer",
238
+ "description": "Insert new cell after this index (for CREATE)",
239
+ },
240
+ },
241
+ "required": ["operation", "content"],
242
+ }
243
+
244
+ @property
245
+ def risk_level(self) -> str:
246
+ return "low"
247
+
248
+ def set_notebook(self, model: dict) -> None:
249
+ """현재 노트북 컨텍스트를 설정합니다."""
250
+ self._notebook_model = model
251
+
252
+ async def execute(self, **kwargs: Any) -> ToolResult:
253
+ """마크다운 셀 작업을 실행합니다."""
254
+ operation = kwargs.get("operation", "").upper()
255
+ content = kwargs.get("content", "")
256
+
257
+ if not self._notebook_model:
258
+ return ToolResult(success=False, error="노트북 컨텍스트가 설정되지 않음")
259
+
260
+ cells = self._notebook_model.get("content", {}).get("cells", [])
261
+
262
+ if operation == "CREATE":
263
+ new_cell = {
264
+ "cell_type": "markdown",
265
+ "source": content,
266
+ "metadata": {},
267
+ }
268
+
269
+ insert_after = kwargs.get("insert_after")
270
+ if insert_after is not None and 0 <= insert_after < len(cells):
271
+ cells.insert(insert_after + 1, new_cell)
272
+ cell_index = insert_after + 1
273
+ else:
274
+ cells.append(new_cell)
275
+ cell_index = len(cells) - 1
276
+
277
+ return ToolResult(
278
+ success=True,
279
+ data={"cell_index": cell_index, "operation": "created"},
280
+ )
281
+
282
+ elif operation == "UPDATE":
283
+ cell_index = kwargs.get("cell_index", 0)
284
+ if not 0 <= cell_index < len(cells):
285
+ return ToolResult(
286
+ success=False, error=f"잘못된 셀 인덱스: {cell_index}"
287
+ )
288
+
289
+ cells[cell_index]["source"] = content
290
+ cells[cell_index]["cell_type"] = "markdown"
291
+ return ToolResult(
292
+ success=True,
293
+ data={"cell_index": cell_index, "operation": "updated"},
294
+ )
295
+
296
+ return ToolResult(success=False, error=f"알 수 없는 연산: {operation}")
@@ -0,0 +1,159 @@
1
+ """도구 관리 및 실행을 위한 도구 레지스트리."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional
6
+
7
+ from codebuff_core.core.exceptions import ToolError
8
+
9
+ from .base import BaseTool, ToolResult
10
+ from .jupyter_tools import JupyterCellTool, JupyterMarkdownTool
11
+ from .file_tools import FileReadTool, FileWriteTool, FileListTool
12
+ from .shell_tools import ShellExecuteTool
13
+ from .search_tools import SearchCodeTool, WebSearchTool
14
+
15
+
16
+ class ToolRegistry:
17
+ """도구 관리를 위한 레지스트리."""
18
+
19
+ # HITL 승인이 필요한 도구들
20
+ HIGH_RISK_TOOLS = {"shell_execute", "file_write"}
21
+
22
+ def __init__(
23
+ self,
24
+ kernel_manager: Any = None,
25
+ contents_manager: Any = None,
26
+ base_path: str = ".",
27
+ tavily_api_key: Optional[str] = None,
28
+ ):
29
+ self._tools: dict[str, BaseTool] = {}
30
+ self._base_path = base_path
31
+ self._kernel_manager = kernel_manager
32
+ self._contents_manager = contents_manager
33
+
34
+ self._register_default_tools(tavily_api_key)
35
+
36
+ def _register_default_tools(self, tavily_api_key: Optional[str] = None) -> None:
37
+ """모든 기본 도구를 등록합니다."""
38
+ # Jupyter 도구
39
+ self._tools["jupyter_cell"] = JupyterCellTool(
40
+ self._kernel_manager,
41
+ self._contents_manager,
42
+ )
43
+ self._tools["jupyter_markdown"] = JupyterMarkdownTool(
44
+ self._contents_manager,
45
+ )
46
+
47
+ # 파일 도구
48
+ self._tools["file_read"] = FileReadTool(
49
+ self._contents_manager,
50
+ self._base_path,
51
+ )
52
+ self._tools["file_write"] = FileWriteTool(
53
+ self._contents_manager,
54
+ self._base_path,
55
+ )
56
+ self._tools["file_list"] = FileListTool(
57
+ self._contents_manager,
58
+ self._base_path,
59
+ )
60
+
61
+ # 셸 도구
62
+ self._tools["shell_execute"] = ShellExecuteTool(
63
+ working_dir=self._base_path,
64
+ )
65
+
66
+ # 검색 도구
67
+ self._tools["search_code"] = SearchCodeTool(
68
+ self._base_path,
69
+ )
70
+ web_search = WebSearchTool(api_key=tavily_api_key)
71
+ self._tools["web_search"] = web_search
72
+
73
+ def register(self, tool: BaseTool) -> None:
74
+ """도구를 등록합니다."""
75
+ self._tools[tool.name] = tool
76
+
77
+ def unregister(self, name: str) -> None:
78
+ """도구 등록을 해제합니다."""
79
+ if name in self._tools:
80
+ del self._tools[name]
81
+
82
+ def get(self, name: str) -> Optional[BaseTool]:
83
+ """이름으로 도구를 가져옵니다."""
84
+ return self._tools.get(name)
85
+
86
+ def get_definitions(self) -> list[dict[str, Any]]:
87
+ """LLM용 모든 도구 정의를 가져옵니다."""
88
+ return [tool.definition for tool in self._tools.values()]
89
+
90
+ def requires_approval(self, name: str, args: dict[str, Any]) -> bool:
91
+ """
92
+ 도구 실행에 HITL 승인이 필요한지 확인합니다.
93
+
94
+ Args:
95
+ name: 도구 이름
96
+ args: 도구 인자
97
+
98
+ Returns:
99
+ 승인이 필요하면 True
100
+ """
101
+ # 고위험 도구인지 확인
102
+ if name in self.HIGH_RISK_TOOLS:
103
+ return True
104
+
105
+ # 도구의 위험 수준 확인
106
+ tool = self._tools.get(name)
107
+ if tool and tool.risk_level in ("high", "critical"):
108
+ return True
109
+
110
+ # 인자 기반 추가 검사
111
+ if name == "jupyter_cell":
112
+ # EXECUTE 작업에 승인 필요
113
+ if args.get("operation", "").upper() == "EXECUTE":
114
+ return True
115
+
116
+ return False
117
+
118
+ async def execute(self, name: str, args: dict[str, Any]) -> ToolResult:
119
+ """
120
+ 도구를 실행합니다.
121
+
122
+ Args:
123
+ name: 도구 이름
124
+ args: 도구 인자
125
+
126
+ Returns:
127
+ 도구 실행 결과 ToolResult
128
+ """
129
+ tool = self._tools.get(name)
130
+ if not tool:
131
+ return ToolResult(success=False, error=f"알 수 없는 도구: {name}")
132
+
133
+ try:
134
+ return await tool.execute(**args)
135
+ except ToolError:
136
+ raise
137
+ except Exception as e:
138
+ raise ToolError(f"도구 실행 오류: {e}", tool_name=name, cause=e) from e
139
+
140
+ def set_notebook_context(self, path: str, model: dict) -> None:
141
+ """Jupyter 도구에 노트북 컨텍스트를 설정합니다."""
142
+ jupyter_cell = self._tools.get("jupyter_cell")
143
+ if isinstance(jupyter_cell, JupyterCellTool):
144
+ jupyter_cell.set_notebook(path, model)
145
+
146
+ jupyter_md = self._tools.get("jupyter_markdown")
147
+ if isinstance(jupyter_md, JupyterMarkdownTool):
148
+ jupyter_md.set_notebook(model)
149
+
150
+ def set_tavily_api_key(self, api_key: str) -> None:
151
+ """웹 검색을 위한 Tavily API 키를 설정합니다."""
152
+ web_search = self._tools.get("web_search")
153
+ if isinstance(web_search, WebSearchTool):
154
+ web_search.set_api_key(api_key)
155
+
156
+ @property
157
+ def tool_names(self) -> list[str]:
158
+ """등록된 도구 이름 목록을 가져옵니다."""
159
+ return list(self._tools.keys())
@@ -0,0 +1,230 @@
1
+ """코드 및 웹 검색 도구."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from pathlib import Path
7
+ from typing import Any, Optional
8
+
9
+ import httpx
10
+
11
+ from .base import BaseTool, ToolResult
12
+
13
+
14
+ class SearchCodeTool(BaseTool):
15
+ """파일에서 코드 검색 도구."""
16
+
17
+ def __init__(self, base_path: str = "."):
18
+ self._base_path = Path(base_path)
19
+
20
+ @property
21
+ def name(self) -> str:
22
+ return "search_code"
23
+
24
+ @property
25
+ def description(self) -> str:
26
+ return """Search for patterns in code files.
27
+ Uses regex patterns to find matches across files.
28
+ Returns matching lines with file paths and line numbers."""
29
+
30
+ @property
31
+ def parameters(self) -> dict[str, Any]:
32
+ return {
33
+ "type": "object",
34
+ "properties": {
35
+ "pattern": {
36
+ "type": "string",
37
+ "description": "Regex pattern to search for",
38
+ },
39
+ "path": {
40
+ "type": "string",
41
+ "description": "Directory to search in (relative to workspace)",
42
+ "default": ".",
43
+ },
44
+ "file_pattern": {
45
+ "type": "string",
46
+ "description": "Glob pattern for files to search (e.g., '*.py')",
47
+ "default": "*",
48
+ },
49
+ "max_results": {
50
+ "type": "integer",
51
+ "description": "Maximum number of results to return",
52
+ "default": 50,
53
+ },
54
+ "case_sensitive": {
55
+ "type": "boolean",
56
+ "description": "Case-sensitive search",
57
+ "default": True,
58
+ },
59
+ },
60
+ "required": ["pattern"],
61
+ }
62
+
63
+ @property
64
+ def risk_level(self) -> str:
65
+ return "low"
66
+
67
+ async def execute(self, **kwargs: Any) -> ToolResult:
68
+ """코드 패턴을 검색합니다."""
69
+ pattern = kwargs.get("pattern", "")
70
+ path = kwargs.get("path", ".")
71
+ file_pattern = kwargs.get("file_pattern", "*")
72
+ max_results = kwargs.get("max_results", 50)
73
+ case_sensitive = kwargs.get("case_sensitive", True)
74
+
75
+ if not pattern:
76
+ return ToolResult(success=False, error="패턴이 필요합니다")
77
+
78
+ try:
79
+ flags = 0 if case_sensitive else re.IGNORECASE
80
+ regex = re.compile(pattern, flags)
81
+ except re.error as e:
82
+ return ToolResult(success=False, error=f"잘못된 정규식 패턴: {e}")
83
+
84
+ search_path = self._base_path / path
85
+ if not search_path.exists():
86
+ return ToolResult(success=False, error=f"경로를 찾을 수 없음: {path}")
87
+
88
+ results = []
89
+ try:
90
+ for file_path in search_path.rglob(file_pattern):
91
+ if not file_path.is_file():
92
+ continue
93
+
94
+ # 바이너리 파일과 숨김 디렉토리 건너뛰기
95
+ if any(part.startswith(".") for part in file_path.parts):
96
+ continue
97
+
98
+ try:
99
+ content = file_path.read_text(encoding="utf-8", errors="ignore")
100
+ for line_num, line in enumerate(content.splitlines(), 1):
101
+ if regex.search(line):
102
+ rel_path = file_path.relative_to(self._base_path)
103
+ results.append(
104
+ {
105
+ "file": str(rel_path),
106
+ "line": line_num,
107
+ "content": line.strip(),
108
+ }
109
+ )
110
+
111
+ if len(results) >= max_results:
112
+ break
113
+ except Exception:
114
+ # 읽을 수 없는 파일 건너뛰기
115
+ continue
116
+
117
+ if len(results) >= max_results:
118
+ break
119
+
120
+ return ToolResult(
121
+ success=True,
122
+ data={"matches": results, "pattern": pattern},
123
+ metadata={
124
+ "count": len(results),
125
+ "truncated": len(results) >= max_results,
126
+ },
127
+ )
128
+
129
+ except Exception as e:
130
+ return ToolResult(success=False, error=f"검색 오류: {str(e)}")
131
+
132
+
133
+ class WebSearchTool(BaseTool):
134
+ """Tavily API를 사용한 웹 검색 도구."""
135
+
136
+ def __init__(self, api_key: Optional[str] = None):
137
+ self._api_key = api_key
138
+ self._client = httpx.AsyncClient(timeout=30.0)
139
+
140
+ @property
141
+ def name(self) -> str:
142
+ return "web_search"
143
+
144
+ @property
145
+ def description(self) -> str:
146
+ return """Search the web for information.
147
+ Returns relevant search results with titles, URLs, and snippets.
148
+ Useful for finding current information, documentation, or examples."""
149
+
150
+ @property
151
+ def parameters(self) -> dict[str, Any]:
152
+ return {
153
+ "type": "object",
154
+ "properties": {
155
+ "query": {
156
+ "type": "string",
157
+ "description": "The search query",
158
+ },
159
+ "max_results": {
160
+ "type": "integer",
161
+ "description": "Maximum number of results to return",
162
+ "default": 5,
163
+ },
164
+ },
165
+ "required": ["query"],
166
+ }
167
+
168
+ @property
169
+ def risk_level(self) -> str:
170
+ return "low"
171
+
172
+ def set_api_key(self, api_key: str) -> None:
173
+ """Tavily API 키를 설정합니다."""
174
+ self._api_key = api_key
175
+
176
+ async def execute(self, **kwargs: Any) -> ToolResult:
177
+ """웹 검색을 실행합니다."""
178
+ query = kwargs.get("query", "")
179
+ max_results = kwargs.get("max_results", 5)
180
+
181
+ if not query:
182
+ return ToolResult(success=False, error="쿼리가 필요합니다")
183
+
184
+ if not self._api_key:
185
+ return ToolResult(success=False, error="Tavily API 키가 설정되지 않음")
186
+
187
+ try:
188
+ response = await self._client.post(
189
+ "https://api.tavily.com/search",
190
+ json={
191
+ "api_key": self._api_key,
192
+ "query": query,
193
+ "max_results": max_results,
194
+ "include_answer": True,
195
+ },
196
+ )
197
+ response.raise_for_status()
198
+
199
+ data = response.json()
200
+ results = []
201
+
202
+ for result in data.get("results", []):
203
+ results.append(
204
+ {
205
+ "title": result.get("title", ""),
206
+ "url": result.get("url", ""),
207
+ "snippet": result.get("content", ""),
208
+ }
209
+ )
210
+
211
+ return ToolResult(
212
+ success=True,
213
+ data={
214
+ "results": results,
215
+ "answer": data.get("answer"),
216
+ "query": query,
217
+ },
218
+ metadata={"count": len(results)},
219
+ )
220
+
221
+ except httpx.HTTPStatusError as e:
222
+ return ToolResult(
223
+ success=False, error=f"검색 API 오류: {e.response.status_code}"
224
+ )
225
+ except Exception as e:
226
+ return ToolResult(success=False, error=f"검색 오류: {str(e)}")
227
+
228
+ async def close(self):
229
+ """HTTP 클라이언트를 닫습니다."""
230
+ await self._client.aclose()