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,116 @@
1
+ """AWS Secrets Manager 부트스트랩 — 연결정보를 환경변수로 주입.
2
+
3
+ HDSP_ENV(dev/prod)에 따라 단일 시크릿에서 vLLM/qdrant/embedding 연결정보를
4
+ 읽어 대응 환경변수로 세팅한다. 소싱 코드(llm/service, models/rag,
5
+ vllm_embedding_service)가 env를 config보다 우선 읽으므로, config 파일에
6
+ 평문 키를 두지 않아도 된다.
7
+
8
+ 동작:
9
+ - HDSP_ENV 미설정(로컬 등): 아무것도 안 함 → 기존 config 파일 방식.
10
+ - 조회 성공: 시크릿 값들을 대응 env로 주입.
11
+ - 조회 실패: **기동은 정상 진행**하고 CODEBUFF_SECRET_LOAD_ERROR env에 사유만
12
+ 기록 → 구 chat_service 가 이 값을 보고 사용자에게 에러로 노출했다. Chat/Q&A 가
13
+ codex 로 옮긴 뒤(이슈 #155)에는 Q&A 검색 도구의 실패로만 드러난다.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import logging
20
+ import os
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # HDSP_ENV → 시크릿 이름 (개발계 WDD / 운영계 WDP)
25
+ _SECRET_NAMES = {"dev": "WDD-BDP-SCM-CODEBUFF", "prod": "WDP-BDP-SCM-CODEBUFF"}
26
+ _REGION = "ap-northeast-2"
27
+ ERROR_ENV = "CODEBUFF_SECRET_LOAD_ERROR"
28
+
29
+ # 시크릿 JSON 키 → 주입할 환경변수 (없는 키는 스킵 → config 파일 값 사용)
30
+ _KEY_MAP = {
31
+ "vllm_endpoint": "VLLM_ENDPOINT",
32
+ "vllm_api_key": "VLLM_API_KEY",
33
+ "vllm_model": "VLLM_MODEL",
34
+ "qdrant_mode": "QDRANT_MODE",
35
+ "qdrant_url": "QDRANT_URL",
36
+ "qdrant_api_key": "QDRANT_API_KEY",
37
+ "embedding_endpoint": "EMBEDDING_ENDPOINT",
38
+ "embedding_api_key": "EMBEDDING_API_KEY",
39
+ "embedding_model": "EMBEDDING_MODEL",
40
+ "embedding_dimension": "EMBEDDING_DIMENSION",
41
+ # codex 서버사이드 접속 (이슈 #88). 없으면 codex 엔진이 드롭다운에서 숨는다.
42
+ # `codex_api_key` 는 codebuff↔centri 통신 전체의 공유 키다 — 로그 적재(#30)·중앙 설정(#113)도
43
+ # 이 키 하나로 인증하고, 주소는 아래 클러스터 DNS 로 정한다 (이슈 #113, 2026-09-25).
44
+ # 커스텀 스킬 동기화(#134)도 같은 키다.
45
+ "codex_endpoint": "CODEX_SERVER_URL",
46
+ "codex_api_key": "CODEX_SERVER_API_KEY",
47
+ }
48
+
49
+ # 개발계·운영계에서 codebuff pod 과 centri 는 같은 hdsp 클러스터에 있어 codex 입구
50
+ # (centri 게이트웨이, centri#1746)의 주소가 클러스터 DNS 로 정해져 있다. 그래서 시크릿에는
51
+ # 키만 두면 된다. 키가 없으면 채우지 않는다 — 채우면 늘 거부되는 엔진이 드롭다운에 뜬다.
52
+ _CENTRI_CLUSTER_HOST = "centri-agent-server.agents.svc.cluster.local:8000"
53
+ _CODEX_CLUSTER_ENDPOINT = f"ws://{_CENTRI_CLUSTER_HOST}/agents/hdsp_rag/codex"
54
+
55
+ # 공유 키가 있으면 채우는 centri 주소들. 이미 env 에 있으면(로컬 개발 등) 덮지 않는다.
56
+ # 키가 없으면 채우지 않는다 — 채우면 늘 401 을 받는 송신이 뜬다.
57
+ _CENTRI_ENDPOINTS = {
58
+ "CODEX_SERVER_URL": _CODEX_CLUSTER_ENDPOINT,
59
+ "CODEBUFF_INGEST_URL": f"http://{_CENTRI_CLUSTER_HOST}/api/v1/codebuff/ingest",
60
+ "CODEBUFF_CONFIG_URL": f"http://{_CENTRI_CLUSTER_HOST}/api/v1/codebuff/config",
61
+ "CODEBUFF_SKILLS_URL": f"http://{_CENTRI_CLUSTER_HOST}/api/v1/codebuff/skills",
62
+ }
63
+
64
+
65
+ def bootstrap_secrets() -> None:
66
+ """HDSP_ENV에 맞는 시크릿을 읽어 연결정보 환경변수로 주입.
67
+
68
+ 서비스/ConfigManager 초기화 전에(확장 로드 시점) 호출해야 한다.
69
+ """
70
+ env = os.environ.get("HDSP_ENV", "").strip().lower()
71
+ secret_name = _SECRET_NAMES.get(env)
72
+ if not secret_name:
73
+ # HDSP_ENV 미설정 → SM 미사용 (로컬 등). 이전 에러 있으면 클리어.
74
+ os.environ.pop(ERROR_ENV, None)
75
+ return
76
+
77
+ try:
78
+ # bdpengine의 검증된 클라이언트 팩토리 사용 — athena/s3와 동일 경로라
79
+ # 그 환경의 유효 AWS 자격증명 + 정상 SSL을 그대로 따른다.
80
+ # (raw boto3 + 기본 자격증명은 환경에 따라 UnrecognizedClientException,
81
+ # 또는 pyOpenSSL↔cryptography 불일치(GEN_EMAIL)로 실패하므로 bdpengine 우선.
82
+ # bdpengine 미설치 환경(로컬 등)에서만 raw boto3로 폴백)
83
+ try:
84
+ from bdpengine.functions import get_boto3_client
85
+
86
+ client = get_boto3_client("secretsmanager")
87
+ except ImportError:
88
+ import boto3
89
+
90
+ client = boto3.client("secretsmanager", region_name=_REGION)
91
+
92
+ raw = client.get_secret_value(SecretId=secret_name)["SecretString"]
93
+ data = json.loads(raw)
94
+
95
+ injected = []
96
+ for skey, envk in _KEY_MAP.items():
97
+ val = data.get(skey)
98
+ if val is not None and str(val) != "":
99
+ os.environ[envk] = str(val)
100
+ injected.append(envk)
101
+ if os.environ.get("CODEX_SERVER_API_KEY"):
102
+ for envk, url in _CENTRI_ENDPOINTS.items():
103
+ if not os.environ.get(envk):
104
+ os.environ[envk] = url
105
+ injected.append(envk)
106
+
107
+ os.environ.pop(ERROR_ENV, None) # 성공 → 이전 에러 클리어
108
+ logger.info(
109
+ "[Secrets] loaded %d connection settings from %s",
110
+ len(injected),
111
+ secret_name,
112
+ )
113
+ except Exception as e: # noqa: BLE001 — 기동을 막지 않기 위해 광범위 캐치
114
+ # 기동은 계속 진행. 채팅 시도 시 노출할 에러만 기록.
115
+ os.environ[ERROR_ENV] = f"{secret_name}: {type(e).__name__}: {e}"
116
+ logger.error("[Secrets] failed to load %s: %s", secret_name, e)
@@ -0,0 +1,105 @@
1
+ """Keycloak 팀 클레임으로 기능 접근을 가른다.
2
+
3
+ codex 엔진은 AI Platform개발실 소속에게만 연다 (이슈 #101). 클레임이 "실 > 팀"
4
+ 꼴인지 팀 이름만인지 확인하지 못해서, 판정은 공백·대소문자를 무시한 부분
5
+ 일치이고 목록은 `CODEBUFF_CODEX_ALLOWED_TEAMS`(쉼표 구분)로 바꿀 수 있다.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import base64
11
+ import json
12
+ import logging
13
+ import os
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ _DEFAULT_CODEX_TEAMS = ("AI Platform개발실", "AI플랫폼개발실")
18
+ # 소속과 무관하게 반드시 여는 사번. 환경변수는 여기에 더할 뿐 빼지 못한다.
19
+ _ALWAYS_CODEX_USERS = ("453180",)
20
+
21
+
22
+ def _read_claims() -> dict:
23
+ """`KEYCLOAK_ID_TOKEN` 의 payload. 토큰이 없거나 깨졌으면 빈 dict."""
24
+ parts = os.environ.get("KEYCLOAK_ID_TOKEN", "").split(".")
25
+ if len(parts) != 3:
26
+ return {}
27
+ try:
28
+ payload = parts[1] + "=" * (-len(parts[1]) % 4)
29
+ claims = json.loads(base64.urlsafe_b64decode(payload))
30
+ except Exception as e:
31
+ logger.warning(f"Failed to decode Keycloak token: {e}")
32
+ return {}
33
+ return claims if isinstance(claims, dict) else {}
34
+
35
+
36
+ def read_user_team() -> str:
37
+ """`KEYCLOAK_ID_TOKEN` 의 팀 클레임. 토큰이 없거나 깨졌으면 빈 문자열."""
38
+ claim_name = os.environ.get("HDSP_TEAM_CLAIM_NAME", "hcc_team")
39
+ value = _read_claims().get(claim_name, "")
40
+ if isinstance(value, list):
41
+ return " ".join(str(v) for v in value)
42
+ return str(value or "")
43
+
44
+
45
+ def resolve_team() -> str:
46
+ """로그에 찍을 팀. `HDSP_USER_TEAM` 이 우선이고, 비면 JWT 팀 클레임 (이슈 #98).
47
+
48
+ env 주입이 빠진 pod 에서도 성과 지표의 팀 축이 빈 문자열이 되지 않게 한다.
49
+ **로그에 팀을 찍는 자리**가 이 함수를 쓴다 — `test_team_fallback` 이 그 파일들의
50
+ 직접 읽기를 막는다. 접근 제어(`codex_allowed`)와 PageConfig 는 env 를 따로 읽는다.
51
+ """
52
+ # 앞뒤 공백을 뗀다 — centri 는 공백 없는 팀 이름으로 층 행을 저장한다 (이슈 #237)
53
+ return os.environ.get("HDSP_USER_TEAM", "").strip() or read_user_team().strip()
54
+
55
+
56
+ def _norm(text: str) -> str:
57
+ return "".join(text.split()).lower()
58
+
59
+
60
+ def user_ids() -> list:
61
+ """사번 후보를 이 순서로. 어느 변수로 오는지 확정하지 못해서 셋 다 본다 (이슈 #101).
62
+
63
+ 커스텀 스킬 동기화(#134)는 첫 값을 centri 에 보내고, 받은 개인 스킬은 후보 중 하나와
64
+ 맞으면 받는다.
65
+ """
66
+ raw = [
67
+ os.environ.get("JUPYTERHUB_USER", ""),
68
+ os.environ.get("HDSP_BDP_ID", ""),
69
+ str(_read_claims().get("preferred_username", "") or ""),
70
+ ]
71
+ out: list = []
72
+ for v in raw:
73
+ v = (v or "").strip()
74
+ if v and v not in out:
75
+ out.append(v)
76
+ return out
77
+
78
+
79
+ def _user_ids() -> set:
80
+ return set(user_ids())
81
+
82
+
83
+ def _codex_user_allowed() -> bool:
84
+ extra = os.environ.get("CODEBUFF_CODEX_ALLOWED_USERS", "").split(",")
85
+ allowed = {u.strip() for u in (*_ALWAYS_CODEX_USERS, *extra) if u.strip()}
86
+ return bool(_user_ids() & allowed)
87
+
88
+
89
+ def codex_allowed() -> bool:
90
+ """팀 클레임이 허용 목록 중 하나를 포함하면 True.
91
+
92
+ 토큰 자체가 없으면 개발 환경으로 보고 True. 토큰은 있는데 깨졌거나 팀
93
+ 클레임이 비었으면 False — 그 경우까지 열면 403 이 우회된다.
94
+ 허용 사번(`_ALWAYS_CODEX_USERS` + `CODEBUFF_CODEX_ALLOWED_USERS`)은 팀과 무관하게 True.
95
+ """
96
+ if _codex_user_allowed():
97
+ return True
98
+ if not os.environ.get("KEYCLOAK_ID_TOKEN", ""):
99
+ return True
100
+ team = read_user_team()
101
+ if not team:
102
+ return False
103
+ raw = os.environ.get("CODEBUFF_CODEX_ALLOWED_TEAMS", "")
104
+ allowed = [t for t in raw.split(",") if t.strip()] or list(_DEFAULT_CODEX_TEAMS)
105
+ return any(_norm(t) in _norm(team) for t in allowed)
@@ -0,0 +1 @@
1
+ """Tests for jupyter_ext package."""
@@ -0,0 +1,234 @@
1
+ """Tests for NotebookContextManager."""
2
+
3
+ import pytest
4
+
5
+ from jupyter_ext.tools.context import (
6
+ CellInfo,
7
+ NotebookContext,
8
+ NotebookContextManager,
9
+ )
10
+
11
+
12
+ class TestCellInfo:
13
+ """Tests for CellInfo dataclass."""
14
+
15
+ def test_cell_info_creation(self):
16
+ """Test CellInfo creation with default values."""
17
+ cell = CellInfo(index=0, cell_type="code", source="print('hello')")
18
+ assert cell.index == 0
19
+ assert cell.cell_type == "code"
20
+ assert cell.source == "print('hello')"
21
+ assert cell.outputs == []
22
+ assert cell.execution_count is None
23
+
24
+ def test_cell_info_with_outputs(self):
25
+ """Test CellInfo with outputs."""
26
+ outputs = [{"output_type": "stream", "text": "hello\n"}]
27
+ cell = CellInfo(
28
+ index=1,
29
+ cell_type="code",
30
+ source="print('hello')",
31
+ outputs=outputs,
32
+ execution_count=5,
33
+ )
34
+ assert cell.outputs == outputs
35
+ assert cell.execution_count == 5
36
+
37
+
38
+ class TestNotebookContext:
39
+ """Tests for NotebookContext dataclass."""
40
+
41
+ def test_empty_notebook(self):
42
+ """Test empty notebook context."""
43
+ ctx = NotebookContext(path="test.ipynb")
44
+ assert ctx.path == "test.ipynb"
45
+ assert ctx.cell_count == 0
46
+ assert ctx.get_code_cells() == []
47
+ assert ctx.get_markdown_cells() == []
48
+
49
+ def test_notebook_with_cells(self):
50
+ """Test notebook with mixed cells."""
51
+ cells = [
52
+ CellInfo(index=0, cell_type="code", source="x = 1"),
53
+ CellInfo(index=1, cell_type="markdown", source="# Header"),
54
+ CellInfo(index=2, cell_type="code", source="print(x)"),
55
+ ]
56
+ ctx = NotebookContext(path="test.ipynb", cells=cells)
57
+
58
+ assert ctx.cell_count == 3
59
+ assert len(ctx.get_code_cells()) == 2
60
+ assert len(ctx.get_markdown_cells()) == 1
61
+
62
+ def test_get_cell(self):
63
+ """Test getting a cell by index."""
64
+ cells = [
65
+ CellInfo(index=0, cell_type="code", source="x = 1"),
66
+ CellInfo(index=1, cell_type="markdown", source="# Header"),
67
+ ]
68
+ ctx = NotebookContext(path="test.ipynb", cells=cells)
69
+
70
+ assert ctx.get_cell(0) is not None
71
+ assert ctx.get_cell(0).source == "x = 1"
72
+ assert ctx.get_cell(1).cell_type == "markdown"
73
+ assert ctx.get_cell(2) is None
74
+ assert ctx.get_cell(-1) is None
75
+
76
+
77
+ class TestNotebookContextManager:
78
+ """Tests for NotebookContextManager."""
79
+
80
+ @pytest.fixture
81
+ def context_manager(self):
82
+ """Create a NotebookContextManager instance."""
83
+ return NotebookContextManager()
84
+
85
+ @pytest.fixture
86
+ def sample_notebook_model(self):
87
+ """Create a sample notebook model."""
88
+ return {
89
+ "content": {
90
+ "cells": [
91
+ {
92
+ "cell_type": "code",
93
+ "source": "import pandas as pd",
94
+ "outputs": [],
95
+ "execution_count": 1,
96
+ "metadata": {},
97
+ },
98
+ {
99
+ "cell_type": "markdown",
100
+ "source": "# Data Analysis",
101
+ "metadata": {},
102
+ },
103
+ {
104
+ "cell_type": "code",
105
+ "source": "df = pd.read_csv('data.csv')",
106
+ "outputs": [],
107
+ "execution_count": 2,
108
+ "metadata": {},
109
+ },
110
+ ],
111
+ "metadata": {
112
+ "kernelspec": {
113
+ "name": "python3",
114
+ "display_name": "Python 3",
115
+ }
116
+ },
117
+ }
118
+ }
119
+
120
+ def test_initial_state(self, context_manager):
121
+ """Test initial state of context manager."""
122
+ assert context_manager.current_notebook is None
123
+ assert context_manager.notebook_model is None
124
+
125
+ def test_set_notebook(self, context_manager, sample_notebook_model):
126
+ """Test setting notebook context."""
127
+ context_manager.set_notebook("test.ipynb", sample_notebook_model)
128
+
129
+ assert context_manager.current_notebook is not None
130
+ assert context_manager.current_notebook.path == "test.ipynb"
131
+ assert context_manager.current_notebook.cell_count == 3
132
+ assert context_manager.current_notebook.kernel_name == "python3"
133
+
134
+ def test_get_summary(self, context_manager, sample_notebook_model):
135
+ """Test getting notebook summary."""
136
+ # Without notebook
137
+ summary = context_manager.get_summary()
138
+ assert summary == {"status": "no_notebook"}
139
+
140
+ # With notebook
141
+ context_manager.set_notebook("test.ipynb", sample_notebook_model)
142
+ summary = context_manager.get_summary()
143
+
144
+ assert summary["path"] == "test.ipynb"
145
+ assert summary["cell_count"] == 3
146
+ assert summary["code_cells"] == 2
147
+ assert summary["markdown_cells"] == 1
148
+ assert summary["kernel"] == "python3"
149
+
150
+ def test_update_cell(self, context_manager, sample_notebook_model):
151
+ """Test updating a cell."""
152
+ context_manager.set_notebook("test.ipynb", sample_notebook_model)
153
+
154
+ # Update existing cell
155
+ assert context_manager.update_cell(0, "import numpy as np")
156
+
157
+ # Verify in notebook model
158
+ cells = context_manager.notebook_model["content"]["cells"]
159
+ assert cells[0]["source"] == "import numpy as np"
160
+
161
+ # Verify in context
162
+ assert context_manager.current_notebook.cells[0].source == "import numpy as np"
163
+
164
+ # Invalid index
165
+ assert not context_manager.update_cell(10, "x = 1")
166
+ assert not context_manager.update_cell(-1, "x = 1")
167
+
168
+ def test_insert_cell(self, context_manager, sample_notebook_model):
169
+ """Test inserting a new cell."""
170
+ context_manager.set_notebook("test.ipynb", sample_notebook_model)
171
+
172
+ # Insert after index 0
173
+ new_index = context_manager.insert_cell(
174
+ source="# New cell",
175
+ cell_type="code",
176
+ insert_after=0,
177
+ )
178
+
179
+ assert new_index == 1
180
+ assert context_manager.current_notebook.cell_count == 4
181
+
182
+ # Append to end
183
+ new_index = context_manager.insert_cell(
184
+ source="print('done')",
185
+ cell_type="code",
186
+ )
187
+
188
+ assert new_index == 4
189
+ assert context_manager.current_notebook.cell_count == 5
190
+
191
+ def test_insert_markdown_cell(self, context_manager, sample_notebook_model):
192
+ """Test inserting a markdown cell."""
193
+ context_manager.set_notebook("test.ipynb", sample_notebook_model)
194
+
195
+ new_index = context_manager.insert_cell(
196
+ source="## Section",
197
+ cell_type="markdown",
198
+ insert_after=1,
199
+ )
200
+
201
+ cells = context_manager.notebook_model["content"]["cells"]
202
+ assert cells[new_index]["cell_type"] == "markdown"
203
+ assert cells[new_index]["source"] == "## Section"
204
+
205
+ def test_delete_cell(self, context_manager, sample_notebook_model):
206
+ """Test deleting a cell."""
207
+ context_manager.set_notebook("test.ipynb", sample_notebook_model)
208
+ initial_count = context_manager.current_notebook.cell_count
209
+
210
+ # Delete middle cell
211
+ assert context_manager.delete_cell(1)
212
+ assert context_manager.current_notebook.cell_count == initial_count - 1
213
+
214
+ # Check remaining cells have correct indices
215
+ for i, cell in enumerate(context_manager.current_notebook.cells):
216
+ assert cell.index == i
217
+
218
+ # Invalid index
219
+ assert not context_manager.delete_cell(10)
220
+
221
+ def test_clear(self, context_manager, sample_notebook_model):
222
+ """Test clearing notebook context."""
223
+ context_manager.set_notebook("test.ipynb", sample_notebook_model)
224
+ assert context_manager.current_notebook is not None
225
+
226
+ context_manager.clear()
227
+
228
+ assert context_manager.current_notebook is None
229
+ assert context_manager.notebook_model is None
230
+
231
+ def test_insert_without_notebook_raises(self, context_manager):
232
+ """Test that insert raises when no notebook is set."""
233
+ with pytest.raises(ValueError, match="No notebook model set"):
234
+ context_manager.insert_cell(source="x = 1")