pythinker-code 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (731) hide show
  1. pythinker_code/CHANGELOG.md +16 -0
  2. pythinker_code/__init__.py +0 -0
  3. pythinker_code/__main__.py +92 -0
  4. pythinker_code/acp/AGENTS.md +93 -0
  5. pythinker_code/acp/__init__.py +13 -0
  6. pythinker_code/acp/convert.py +128 -0
  7. pythinker_code/acp/host.py +298 -0
  8. pythinker_code/acp/mcp.py +46 -0
  9. pythinker_code/acp/server.py +497 -0
  10. pythinker_code/acp/session.py +496 -0
  11. pythinker_code/acp/tools.py +167 -0
  12. pythinker_code/acp/types.py +13 -0
  13. pythinker_code/acp/version.py +45 -0
  14. pythinker_code/agents/default/agent.yaml +36 -0
  15. pythinker_code/agents/default/coder.yaml +25 -0
  16. pythinker_code/agents/default/explore.yaml +46 -0
  17. pythinker_code/agents/default/plan.yaml +30 -0
  18. pythinker_code/agents/default/system.md +164 -0
  19. pythinker_code/agents/okabe/agent.yaml +22 -0
  20. pythinker_code/agentspec.py +163 -0
  21. pythinker_code/app.py +820 -0
  22. pythinker_code/approval_runtime/__init__.py +29 -0
  23. pythinker_code/approval_runtime/models.py +42 -0
  24. pythinker_code/approval_runtime/runtime.py +235 -0
  25. pythinker_code/auth/__init__.py +25 -0
  26. pythinker_code/auth/anthropic_direct.py +207 -0
  27. pythinker_code/auth/deepseek.py +192 -0
  28. pythinker_code/auth/lm_studio.py +418 -0
  29. pythinker_code/auth/minimax.py +203 -0
  30. pythinker_code/auth/oauth.py +1122 -0
  31. pythinker_code/auth/ollama.py +293 -0
  32. pythinker_code/auth/openai.py +771 -0
  33. pythinker_code/auth/opencode_go.py +203 -0
  34. pythinker_code/auth/openrouter.py +225 -0
  35. pythinker_code/auth/platforms.py +466 -0
  36. pythinker_code/background/__init__.py +36 -0
  37. pythinker_code/background/agent_runner.py +231 -0
  38. pythinker_code/background/ids.py +19 -0
  39. pythinker_code/background/manager.py +650 -0
  40. pythinker_code/background/models.py +105 -0
  41. pythinker_code/background/store.py +237 -0
  42. pythinker_code/background/summary.py +66 -0
  43. pythinker_code/background/worker.py +209 -0
  44. pythinker_code/cli/__init__.py +1326 -0
  45. pythinker_code/cli/__main__.py +19 -0
  46. pythinker_code/cli/_lazy_group.py +238 -0
  47. pythinker_code/cli/export.py +322 -0
  48. pythinker_code/cli/info.py +62 -0
  49. pythinker_code/cli/mcp.py +349 -0
  50. pythinker_code/cli/plugin.py +351 -0
  51. pythinker_code/cli/toad.py +74 -0
  52. pythinker_code/cli/vis.py +38 -0
  53. pythinker_code/cli/web.py +80 -0
  54. pythinker_code/config.py +453 -0
  55. pythinker_code/constant.py +33 -0
  56. pythinker_code/exception.py +43 -0
  57. pythinker_code/hooks/__init__.py +4 -0
  58. pythinker_code/hooks/config.py +34 -0
  59. pythinker_code/hooks/engine.py +371 -0
  60. pythinker_code/hooks/events.py +190 -0
  61. pythinker_code/hooks/runner.py +89 -0
  62. pythinker_code/llm.py +412 -0
  63. pythinker_code/metadata.py +79 -0
  64. pythinker_code/notifications/__init__.py +33 -0
  65. pythinker_code/notifications/llm.py +77 -0
  66. pythinker_code/notifications/manager.py +145 -0
  67. pythinker_code/notifications/models.py +50 -0
  68. pythinker_code/notifications/notifier.py +41 -0
  69. pythinker_code/notifications/store.py +118 -0
  70. pythinker_code/notifications/wire.py +21 -0
  71. pythinker_code/plugin/__init__.py +124 -0
  72. pythinker_code/plugin/manager.py +153 -0
  73. pythinker_code/plugin/tool.py +173 -0
  74. pythinker_code/prompts/__init__.py +6 -0
  75. pythinker_code/prompts/compact.md +73 -0
  76. pythinker_code/prompts/init.md +21 -0
  77. pythinker_code/py.typed +0 -0
  78. pythinker_code/session.py +319 -0
  79. pythinker_code/session_fork.py +325 -0
  80. pythinker_code/session_state.py +132 -0
  81. pythinker_code/share.py +14 -0
  82. pythinker_code/skill/__init__.py +727 -0
  83. pythinker_code/skill/flow/__init__.py +99 -0
  84. pythinker_code/skill/flow/d2.py +482 -0
  85. pythinker_code/skill/flow/mermaid.py +266 -0
  86. pythinker_code/skills/pythinker-code-help/SKILL.md +54 -0
  87. pythinker_code/skills/skill-creator/SKILL.md +367 -0
  88. pythinker_code/soul/__init__.py +304 -0
  89. pythinker_code/soul/agent.py +520 -0
  90. pythinker_code/soul/approval.py +267 -0
  91. pythinker_code/soul/btw.py +214 -0
  92. pythinker_code/soul/compaction.py +189 -0
  93. pythinker_code/soul/context.py +339 -0
  94. pythinker_code/soul/denwarenji.py +39 -0
  95. pythinker_code/soul/dynamic_injection.py +84 -0
  96. pythinker_code/soul/dynamic_injections/__init__.py +0 -0
  97. pythinker_code/soul/dynamic_injections/auto_mode.py +72 -0
  98. pythinker_code/soul/dynamic_injections/plan_mode.py +239 -0
  99. pythinker_code/soul/message.py +92 -0
  100. pythinker_code/soul/pythinkersoul.py +1613 -0
  101. pythinker_code/soul/slash.py +340 -0
  102. pythinker_code/soul/toolset.py +788 -0
  103. pythinker_code/subagents/__init__.py +21 -0
  104. pythinker_code/subagents/builder.py +42 -0
  105. pythinker_code/subagents/core.py +86 -0
  106. pythinker_code/subagents/git_context.py +172 -0
  107. pythinker_code/subagents/models.py +54 -0
  108. pythinker_code/subagents/output.py +71 -0
  109. pythinker_code/subagents/registry.py +28 -0
  110. pythinker_code/subagents/runner.py +428 -0
  111. pythinker_code/subagents/store.py +196 -0
  112. pythinker_code/telemetry/__init__.py +211 -0
  113. pythinker_code/telemetry/config.py +54 -0
  114. pythinker_code/telemetry/crash.py +157 -0
  115. pythinker_code/telemetry/metrics.py +208 -0
  116. pythinker_code/telemetry/otel.py +240 -0
  117. pythinker_code/telemetry/sentry.py +167 -0
  118. pythinker_code/telemetry/sink.py +189 -0
  119. pythinker_code/tools/AGENTS.md +6 -0
  120. pythinker_code/tools/__init__.py +105 -0
  121. pythinker_code/tools/agent/__init__.py +277 -0
  122. pythinker_code/tools/agent/description.md +41 -0
  123. pythinker_code/tools/ask_user/__init__.py +159 -0
  124. pythinker_code/tools/ask_user/description.md +19 -0
  125. pythinker_code/tools/background/__init__.py +318 -0
  126. pythinker_code/tools/background/list.md +10 -0
  127. pythinker_code/tools/background/output.md +11 -0
  128. pythinker_code/tools/background/stop.md +8 -0
  129. pythinker_code/tools/display.py +46 -0
  130. pythinker_code/tools/dmail/__init__.py +38 -0
  131. pythinker_code/tools/dmail/dmail.md +17 -0
  132. pythinker_code/tools/file/__init__.py +30 -0
  133. pythinker_code/tools/file/glob.md +17 -0
  134. pythinker_code/tools/file/glob.py +160 -0
  135. pythinker_code/tools/file/grep.md +6 -0
  136. pythinker_code/tools/file/grep_local.py +589 -0
  137. pythinker_code/tools/file/plan_mode.py +45 -0
  138. pythinker_code/tools/file/read.md +16 -0
  139. pythinker_code/tools/file/read.py +300 -0
  140. pythinker_code/tools/file/read_media.md +24 -0
  141. pythinker_code/tools/file/read_media.py +217 -0
  142. pythinker_code/tools/file/replace.md +7 -0
  143. pythinker_code/tools/file/replace.py +195 -0
  144. pythinker_code/tools/file/utils.py +257 -0
  145. pythinker_code/tools/file/write.md +5 -0
  146. pythinker_code/tools/file/write.py +177 -0
  147. pythinker_code/tools/plan/__init__.py +327 -0
  148. pythinker_code/tools/plan/description.md +29 -0
  149. pythinker_code/tools/plan/enter.py +190 -0
  150. pythinker_code/tools/plan/enter_description.md +35 -0
  151. pythinker_code/tools/plan/heroes.py +277 -0
  152. pythinker_code/tools/shell/__init__.py +253 -0
  153. pythinker_code/tools/shell/bash.md +35 -0
  154. pythinker_code/tools/shell/powershell.md +30 -0
  155. pythinker_code/tools/test.py +55 -0
  156. pythinker_code/tools/think/__init__.py +21 -0
  157. pythinker_code/tools/think/think.md +1 -0
  158. pythinker_code/tools/todo/__init__.py +168 -0
  159. pythinker_code/tools/todo/set_todo_list.md +23 -0
  160. pythinker_code/tools/utils.py +199 -0
  161. pythinker_code/tools/web/__init__.py +4 -0
  162. pythinker_code/tools/web/fetch.md +1 -0
  163. pythinker_code/tools/web/fetch.py +189 -0
  164. pythinker_code/tools/web/search.md +1 -0
  165. pythinker_code/tools/web/search.py +163 -0
  166. pythinker_code/ui/__init__.py +0 -0
  167. pythinker_code/ui/acp/__init__.py +99 -0
  168. pythinker_code/ui/print/__init__.py +474 -0
  169. pythinker_code/ui/print/visualize.py +185 -0
  170. pythinker_code/ui/shell/__init__.py +1696 -0
  171. pythinker_code/ui/shell/console.py +109 -0
  172. pythinker_code/ui/shell/debug.py +190 -0
  173. pythinker_code/ui/shell/echo.py +17 -0
  174. pythinker_code/ui/shell/export_import.py +117 -0
  175. pythinker_code/ui/shell/keyboard.py +300 -0
  176. pythinker_code/ui/shell/mcp_status.py +113 -0
  177. pythinker_code/ui/shell/model_picker.py +318 -0
  178. pythinker_code/ui/shell/oauth.py +272 -0
  179. pythinker_code/ui/shell/placeholders.py +531 -0
  180. pythinker_code/ui/shell/prompt.py +2278 -0
  181. pythinker_code/ui/shell/replay.py +215 -0
  182. pythinker_code/ui/shell/session_picker.py +227 -0
  183. pythinker_code/ui/shell/setup.py +212 -0
  184. pythinker_code/ui/shell/slash.py +898 -0
  185. pythinker_code/ui/shell/startup.py +32 -0
  186. pythinker_code/ui/shell/task_browser.py +486 -0
  187. pythinker_code/ui/shell/update.py +350 -0
  188. pythinker_code/ui/shell/usage.py +291 -0
  189. pythinker_code/ui/shell/usage_adapters/__init__.py +50 -0
  190. pythinker_code/ui/shell/usage_adapters/anthropic_admin.py +233 -0
  191. pythinker_code/ui/shell/usage_adapters/base.py +72 -0
  192. pythinker_code/ui/shell/usage_adapters/deepseek.py +137 -0
  193. pythinker_code/ui/shell/usage_adapters/minimax.py +236 -0
  194. pythinker_code/ui/shell/usage_adapters/openai_admin.py +225 -0
  195. pythinker_code/ui/shell/usage_adapters/openai_chatgpt.py +241 -0
  196. pythinker_code/ui/shell/usage_adapters/opencode_go.py +232 -0
  197. pythinker_code/ui/shell/usage_adapters/openrouter.py +105 -0
  198. pythinker_code/ui/shell/usage_adapters/pythinker.py +189 -0
  199. pythinker_code/ui/shell/usage_adapters/pythinker_ai.py +50 -0
  200. pythinker_code/ui/shell/usage_render.py +150 -0
  201. pythinker_code/ui/shell/visualize/__init__.py +165 -0
  202. pythinker_code/ui/shell/visualize/_approval_panel.py +505 -0
  203. pythinker_code/ui/shell/visualize/_blocks.py +629 -0
  204. pythinker_code/ui/shell/visualize/_btw_panel.py +224 -0
  205. pythinker_code/ui/shell/visualize/_input_router.py +48 -0
  206. pythinker_code/ui/shell/visualize/_interactive.py +523 -0
  207. pythinker_code/ui/shell/visualize/_live_view.py +826 -0
  208. pythinker_code/ui/shell/visualize/_question_panel.py +586 -0
  209. pythinker_code/ui/theme.py +241 -0
  210. pythinker_code/usage_ratelimit_cache.py +175 -0
  211. pythinker_code/utils/__init__.py +0 -0
  212. pythinker_code/utils/aiohttp.py +24 -0
  213. pythinker_code/utils/aioqueue.py +72 -0
  214. pythinker_code/utils/broadcast.py +37 -0
  215. pythinker_code/utils/changelog.py +108 -0
  216. pythinker_code/utils/clipboard.py +246 -0
  217. pythinker_code/utils/datetime.py +64 -0
  218. pythinker_code/utils/diff.py +135 -0
  219. pythinker_code/utils/editor.py +91 -0
  220. pythinker_code/utils/environment.py +73 -0
  221. pythinker_code/utils/envvar.py +22 -0
  222. pythinker_code/utils/export.py +696 -0
  223. pythinker_code/utils/file_filter.py +375 -0
  224. pythinker_code/utils/frontmatter.py +70 -0
  225. pythinker_code/utils/io.py +27 -0
  226. pythinker_code/utils/logging.py +146 -0
  227. pythinker_code/utils/media_tags.py +29 -0
  228. pythinker_code/utils/message.py +24 -0
  229. pythinker_code/utils/path.py +199 -0
  230. pythinker_code/utils/proctitle.py +33 -0
  231. pythinker_code/utils/proxy.py +31 -0
  232. pythinker_code/utils/pyinstaller.py +45 -0
  233. pythinker_code/utils/rich/__init__.py +33 -0
  234. pythinker_code/utils/rich/columns.py +99 -0
  235. pythinker_code/utils/rich/diff_render.py +481 -0
  236. pythinker_code/utils/rich/markdown.py +900 -0
  237. pythinker_code/utils/rich/markdown_sample.md +108 -0
  238. pythinker_code/utils/rich/markdown_sample_short.md +2 -0
  239. pythinker_code/utils/rich/syntax.py +114 -0
  240. pythinker_code/utils/sensitive.py +54 -0
  241. pythinker_code/utils/server.py +121 -0
  242. pythinker_code/utils/signals.py +43 -0
  243. pythinker_code/utils/slashcmd.py +124 -0
  244. pythinker_code/utils/string.py +41 -0
  245. pythinker_code/utils/subprocess_env.py +73 -0
  246. pythinker_code/utils/term.py +168 -0
  247. pythinker_code/utils/typing.py +20 -0
  248. pythinker_code/vis/__init__.py +0 -0
  249. pythinker_code/vis/api/__init__.py +5 -0
  250. pythinker_code/vis/api/sessions.py +687 -0
  251. pythinker_code/vis/api/statistics.py +209 -0
  252. pythinker_code/vis/api/system.py +19 -0
  253. pythinker_code/vis/app.py +175 -0
  254. pythinker_code/vis/static/assets/highlighted-body-B3W2YXNL-D2MTYyJz.js +1 -0
  255. pythinker_code/vis/static/assets/index-CezafTt_.js +185 -0
  256. pythinker_code/vis/static/assets/index-DSRInNnm.css +1 -0
  257. pythinker_code/vis/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  258. pythinker_code/vis/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  259. pythinker_code/vis/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  260. pythinker_code/vis/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  261. pythinker_code/vis/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  262. pythinker_code/vis/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  263. pythinker_code/vis/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  264. pythinker_code/vis/static/index.html +17 -0
  265. pythinker_code/web/__init__.py +5 -0
  266. pythinker_code/web/api/__init__.py +15 -0
  267. pythinker_code/web/api/config.py +208 -0
  268. pythinker_code/web/api/open_in.py +199 -0
  269. pythinker_code/web/api/sessions.py +1225 -0
  270. pythinker_code/web/app.py +451 -0
  271. pythinker_code/web/auth.py +191 -0
  272. pythinker_code/web/models.py +98 -0
  273. pythinker_code/web/runner/__init__.py +5 -0
  274. pythinker_code/web/runner/messages.py +57 -0
  275. pythinker_code/web/runner/process.py +754 -0
  276. pythinker_code/web/runner/worker.py +97 -0
  277. pythinker_code/web/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  278. pythinker_code/web/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  279. pythinker_code/web/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  280. pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  281. pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  282. pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  283. pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  284. pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  285. pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  286. pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  287. pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  288. pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  289. pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  290. pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  291. pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  292. pythinker_code/web/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  293. pythinker_code/web/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  294. pythinker_code/web/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  295. pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  296. pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  297. pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  298. pythinker_code/web/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  299. pythinker_code/web/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  300. pythinker_code/web/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  301. pythinker_code/web/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  302. pythinker_code/web/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  303. pythinker_code/web/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  304. pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  305. pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  306. pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  307. pythinker_code/web/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  308. pythinker_code/web/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  309. pythinker_code/web/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  310. pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  311. pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  312. pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  313. pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  314. pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  315. pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  316. pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  317. pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  318. pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  319. pythinker_code/web/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  320. pythinker_code/web/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  321. pythinker_code/web/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  322. pythinker_code/web/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  323. pythinker_code/web/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  324. pythinker_code/web/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  325. pythinker_code/web/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  326. pythinker_code/web/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  327. pythinker_code/web/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  328. pythinker_code/web/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  329. pythinker_code/web/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  330. pythinker_code/web/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  331. pythinker_code/web/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  332. pythinker_code/web/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  333. pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  334. pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  335. pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  336. pythinker_code/web/static/assets/_baseUniq--dyU3g5v.js +1 -0
  337. pythinker_code/web/static/assets/abap-BdImnpbu.js +1 -0
  338. pythinker_code/web/static/assets/actionscript-3-CfeIJUat.js +1 -0
  339. pythinker_code/web/static/assets/ada-bCR0ucgS.js +1 -0
  340. pythinker_code/web/static/assets/andromeeda-C-Jbm3Hp.js +1 -0
  341. pythinker_code/web/static/assets/angular-html-CU67Zn6k.js +1 -0
  342. pythinker_code/web/static/assets/angular-ts-BwZT4LLn.js +1 -0
  343. pythinker_code/web/static/assets/apache-Pmp26Uib.js +1 -0
  344. pythinker_code/web/static/assets/apex-D8_7TLub.js +1 -0
  345. pythinker_code/web/static/assets/apl-dKokRX4l.js +1 -0
  346. pythinker_code/web/static/assets/applescript-Co6uUVPk.js +1 -0
  347. pythinker_code/web/static/assets/ara-BRHolxvo.js +1 -0
  348. pythinker_code/web/static/assets/arc-DkMjLpYa.js +1 -0
  349. pythinker_code/web/static/assets/architectureDiagram-VXUJARFQ-CHWVaGo9.js +36 -0
  350. pythinker_code/web/static/assets/asciidoc-Dv7Oe6Be.js +1 -0
  351. pythinker_code/web/static/assets/asm-D_Q5rh1f.js +1 -0
  352. pythinker_code/web/static/assets/astro-CbQHKStN.js +1 -0
  353. pythinker_code/web/static/assets/aurora-x-D-2ljcwZ.js +1 -0
  354. pythinker_code/web/static/assets/awk-DMzUqQB5.js +1 -0
  355. pythinker_code/web/static/assets/ayu-dark-CmMr59Fi.js +1 -0
  356. pythinker_code/web/static/assets/ballerina-BFfxhgS-.js +1 -0
  357. pythinker_code/web/static/assets/bat-BkioyH1T.js +1 -0
  358. pythinker_code/web/static/assets/beancount-k_qm7-4y.js +1 -0
  359. pythinker_code/web/static/assets/berry-uYugtg8r.js +1 -0
  360. pythinker_code/web/static/assets/bibtex-CHM0blh-.js +1 -0
  361. pythinker_code/web/static/assets/bicep-Bmn6On1c.js +1 -0
  362. pythinker_code/web/static/assets/blade-D4QpJJKB.js +1 -0
  363. pythinker_code/web/static/assets/blockDiagram-VD42YOAC-DzdKe497.js +122 -0
  364. pythinker_code/web/static/assets/bsl-BO_Y6i37.js +1 -0
  365. pythinker_code/web/static/assets/c-BIGW1oBm.js +1 -0
  366. pythinker_code/web/static/assets/c3-VCDPK7BO.js +1 -0
  367. pythinker_code/web/static/assets/c4Diagram-YG6GDRKO-D84Blotg.js +10 -0
  368. pythinker_code/web/static/assets/cadence-Bv_4Rxtq.js +1 -0
  369. pythinker_code/web/static/assets/cairo-KRGpt6FW.js +1 -0
  370. pythinker_code/web/static/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  371. pythinker_code/web/static/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  372. pythinker_code/web/static/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  373. pythinker_code/web/static/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  374. pythinker_code/web/static/assets/channel-CllSjjdl.js +1 -0
  375. pythinker_code/web/static/assets/chunk-4BX2VUAB-C9w8wleE.js +1 -0
  376. pythinker_code/web/static/assets/chunk-55IACEB6-YlYJ8HnF.js +1 -0
  377. pythinker_code/web/static/assets/chunk-B4BG7PRW-Bwtz_AHU.js +165 -0
  378. pythinker_code/web/static/assets/chunk-DI55MBZ5-BbEHkl8h.js +220 -0
  379. pythinker_code/web/static/assets/chunk-FMBD7UC4-BKPbvjLC.js +15 -0
  380. pythinker_code/web/static/assets/chunk-QN33PNHL-D73dQvKf.js +1 -0
  381. pythinker_code/web/static/assets/chunk-QZHKN3VN-zGiLKes_.js +1 -0
  382. pythinker_code/web/static/assets/chunk-TZMSLE5B-LHJCi2fy.js +1 -0
  383. pythinker_code/web/static/assets/clarity-D53aC0YG.js +1 -0
  384. pythinker_code/web/static/assets/classDiagram-2ON5EDUG-vX27iZwa.js +1 -0
  385. pythinker_code/web/static/assets/classDiagram-v2-WZHVMYZB-vX27iZwa.js +1 -0
  386. pythinker_code/web/static/assets/clojure-P80f7IUj.js +1 -0
  387. pythinker_code/web/static/assets/clone-DYBkaPm2.js +1 -0
  388. pythinker_code/web/static/assets/cmake-D1j8_8rp.js +1 -0
  389. pythinker_code/web/static/assets/cobol-nwyudZeR.js +1 -0
  390. pythinker_code/web/static/assets/code-block-IT6T5CEO-NtKViZGl.js +2 -0
  391. pythinker_code/web/static/assets/codeowners-Bp6g37R7.js +1 -0
  392. pythinker_code/web/static/assets/codeql-DsOJ9woJ.js +1 -0
  393. pythinker_code/web/static/assets/coffee-Ch7k5sss.js +1 -0
  394. pythinker_code/web/static/assets/common-lisp-Cg-RD9OK.js +1 -0
  395. pythinker_code/web/static/assets/coq-DkFqJrB1.js +1 -0
  396. pythinker_code/web/static/assets/cose-bilkent-S5V4N54A-DialjZpd.js +1 -0
  397. pythinker_code/web/static/assets/cpp-CofmeUqb.js +1 -0
  398. pythinker_code/web/static/assets/crystal-tKQVLTB8.js +1 -0
  399. pythinker_code/web/static/assets/csharp-K5feNrxe.js +1 -0
  400. pythinker_code/web/static/assets/css-DPfMkruS.js +1 -0
  401. pythinker_code/web/static/assets/csv-fuZLfV_i.js +1 -0
  402. pythinker_code/web/static/assets/cue-D82EKSYY.js +1 -0
  403. pythinker_code/web/static/assets/cypher-COkxafJQ.js +1 -0
  404. pythinker_code/web/static/assets/cytoscape.esm-C_Fzpdck.js +321 -0
  405. pythinker_code/web/static/assets/d-85-TOEBH.js +1 -0
  406. pythinker_code/web/static/assets/dagre-6UL2VRFP-DfuvkZZ7.js +4 -0
  407. pythinker_code/web/static/assets/dark-plus-C3mMm8J8.js +1 -0
  408. pythinker_code/web/static/assets/dart-CF10PKvl.js +1 -0
  409. pythinker_code/web/static/assets/dax-CEL-wOlO.js +1 -0
  410. pythinker_code/web/static/assets/defaultLocale-DX6XiGOO.js +1 -0
  411. pythinker_code/web/static/assets/desktop-BmXAJ9_W.js +1 -0
  412. pythinker_code/web/static/assets/diagram-PSM6KHXK-DLGctX3v.js +24 -0
  413. pythinker_code/web/static/assets/diagram-QEK2KX5R-DnxN6S0F.js +43 -0
  414. pythinker_code/web/static/assets/diagram-S2PKOQOG-Caq_Set9.js +24 -0
  415. pythinker_code/web/static/assets/diff-D97Zzqfu.js +1 -0
  416. pythinker_code/web/static/assets/docker-BcOcwvcX.js +1 -0
  417. pythinker_code/web/static/assets/dotenv-Da5cRb03.js +1 -0
  418. pythinker_code/web/static/assets/dracula-BzJJZx-M.js +1 -0
  419. pythinker_code/web/static/assets/dracula-soft-BXkSAIEj.js +1 -0
  420. pythinker_code/web/static/assets/dream-maker-BtqSS_iP.js +1 -0
  421. pythinker_code/web/static/assets/edge-BkV0erSs.js +1 -0
  422. pythinker_code/web/static/assets/elixir-CDX3lj18.js +1 -0
  423. pythinker_code/web/static/assets/elm-DbKCFpqz.js +1 -0
  424. pythinker_code/web/static/assets/emacs-lisp-C9XAeP06.js +1 -0
  425. pythinker_code/web/static/assets/erDiagram-Q2GNP2WA-BgTfALoK.js +60 -0
  426. pythinker_code/web/static/assets/erb-BOJIQeun.js +1 -0
  427. pythinker_code/web/static/assets/erlang-DsQrWhSR.js +1 -0
  428. pythinker_code/web/static/assets/everforest-dark-BgDCqdQA.js +1 -0
  429. pythinker_code/web/static/assets/everforest-light-C8M2exoo.js +1 -0
  430. pythinker_code/web/static/assets/fennel-BYunw83y.js +1 -0
  431. pythinker_code/web/static/assets/fish-BvzEVeQv.js +1 -0
  432. pythinker_code/web/static/assets/flowDiagram-NV44I4VS-QjW_fnGi.js +162 -0
  433. pythinker_code/web/static/assets/fluent-C4IJs8-o.js +1 -0
  434. pythinker_code/web/static/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  435. pythinker_code/web/static/assets/fortran-free-form-BxgE0vQu.js +1 -0
  436. pythinker_code/web/static/assets/fsharp-CXgrBDvD.js +1 -0
  437. pythinker_code/web/static/assets/ganttDiagram-JELNMOA3-fqi8JFof.js +267 -0
  438. pythinker_code/web/static/assets/gdresource-B7Tvp0Sc.js +1 -0
  439. pythinker_code/web/static/assets/gdscript-DTMYz4Jt.js +1 -0
  440. pythinker_code/web/static/assets/gdshader-DkwncUOv.js +1 -0
  441. pythinker_code/web/static/assets/genie-D0YGMca9.js +1 -0
  442. pythinker_code/web/static/assets/gherkin-DyxjwDmM.js +1 -0
  443. pythinker_code/web/static/assets/git-commit-F4YmCXRG.js +1 -0
  444. pythinker_code/web/static/assets/git-rebase-r7XF79zn.js +1 -0
  445. pythinker_code/web/static/assets/gitGraphDiagram-NY62KEGX-i7o6VQ8x.js +65 -0
  446. pythinker_code/web/static/assets/github-dark-DHJKELXO.js +1 -0
  447. pythinker_code/web/static/assets/github-dark-default-Cuk6v7N8.js +1 -0
  448. pythinker_code/web/static/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  449. pythinker_code/web/static/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  450. pythinker_code/web/static/assets/github-light-DAi9KRSo.js +1 -0
  451. pythinker_code/web/static/assets/github-light-default-D7oLnXFd.js +1 -0
  452. pythinker_code/web/static/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  453. pythinker_code/web/static/assets/gleam-BspZqrRM.js +1 -0
  454. pythinker_code/web/static/assets/glimmer-js-Rg0-pVw9.js +1 -0
  455. pythinker_code/web/static/assets/glimmer-ts-U6CK756n.js +1 -0
  456. pythinker_code/web/static/assets/glsl-DplSGwfg.js +1 -0
  457. pythinker_code/web/static/assets/gn-n2N0HUVH.js +1 -0
  458. pythinker_code/web/static/assets/gnuplot-DdkO51Og.js +1 -0
  459. pythinker_code/web/static/assets/go-Dn2_MT6a.js +1 -0
  460. pythinker_code/web/static/assets/graph-C0vZK2pT.js +1 -0
  461. pythinker_code/web/static/assets/graphql-ChdNCCLP.js +1 -0
  462. pythinker_code/web/static/assets/groovy-gcz8RCvz.js +1 -0
  463. pythinker_code/web/static/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  464. pythinker_code/web/static/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  465. pythinker_code/web/static/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  466. pythinker_code/web/static/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  467. pythinker_code/web/static/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  468. pythinker_code/web/static/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  469. pythinker_code/web/static/assets/hack-CaT9iCJl.js +1 -0
  470. pythinker_code/web/static/assets/haml-B8DHNrY2.js +1 -0
  471. pythinker_code/web/static/assets/handlebars-BL8al0AC.js +1 -0
  472. pythinker_code/web/static/assets/haskell-Df6bDoY_.js +1 -0
  473. pythinker_code/web/static/assets/haxe-CzTSHFRz.js +1 -0
  474. pythinker_code/web/static/assets/hcl-BWvSN4gD.js +1 -0
  475. pythinker_code/web/static/assets/hjson-D5-asLiD.js +1 -0
  476. pythinker_code/web/static/assets/hlsl-D3lLCCz7.js +1 -0
  477. pythinker_code/web/static/assets/houston-DnULxvSX.js +1 -0
  478. pythinker_code/web/static/assets/html-GMplVEZG.js +1 -0
  479. pythinker_code/web/static/assets/html-derivative-BFtXZ54Q.js +1 -0
  480. pythinker_code/web/static/assets/http-jrhK8wxY.js +1 -0
  481. pythinker_code/web/static/assets/hurl-irOxFIW8.js +1 -0
  482. pythinker_code/web/static/assets/hxml-Bvhsp5Yf.js +1 -0
  483. pythinker_code/web/static/assets/hy-DFXneXwc.js +1 -0
  484. pythinker_code/web/static/assets/imba-DGztddWO.js +1 -0
  485. pythinker_code/web/static/assets/index-BYCCk6-K.js +153 -0
  486. pythinker_code/web/static/assets/index-BpoLgcEt.js +1 -0
  487. pythinker_code/web/static/assets/index-Cpy4G3uJ.js +2 -0
  488. pythinker_code/web/static/assets/index-CzV_vCfu.css +1 -0
  489. pythinker_code/web/static/assets/index-DI2oedCt.js +19 -0
  490. pythinker_code/web/static/assets/index-DdIkp80K.js +5 -0
  491. pythinker_code/web/static/assets/infoDiagram-WHAUD3N6-BMPpeZSM.js +2 -0
  492. pythinker_code/web/static/assets/ini-BEwlwnbL.js +1 -0
  493. pythinker_code/web/static/assets/init-Gi6I4Gst.js +1 -0
  494. pythinker_code/web/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  495. pythinker_code/web/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  496. pythinker_code/web/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  497. pythinker_code/web/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  498. pythinker_code/web/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  499. pythinker_code/web/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  500. pythinker_code/web/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  501. pythinker_code/web/static/assets/java-CylS5w8V.js +1 -0
  502. pythinker_code/web/static/assets/javascript-wDzz0qaB.js +1 -0
  503. pythinker_code/web/static/assets/jinja-4LBKfQ-Z.js +1 -0
  504. pythinker_code/web/static/assets/jison-wvAkD_A8.js +1 -0
  505. pythinker_code/web/static/assets/journeyDiagram-XKPGCS4Q-DAM7gngo.js +139 -0
  506. pythinker_code/web/static/assets/json-Cp-IABpG.js +1 -0
  507. pythinker_code/web/static/assets/json5-C9tS-k6U.js +1 -0
  508. pythinker_code/web/static/assets/jsonc-Des-eS-w.js +1 -0
  509. pythinker_code/web/static/assets/jsonl-DcaNXYhu.js +1 -0
  510. pythinker_code/web/static/assets/jsonnet-DFQXde-d.js +1 -0
  511. pythinker_code/web/static/assets/jssm-C2t-YnRu.js +1 -0
  512. pythinker_code/web/static/assets/jsx-g9-lgVsj.js +1 -0
  513. pythinker_code/web/static/assets/julia-CxzCAyBv.js +1 -0
  514. pythinker_code/web/static/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  515. pythinker_code/web/static/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  516. pythinker_code/web/static/assets/kanagawa-wave-DWedfzmr.js +1 -0
  517. pythinker_code/web/static/assets/kanban-definition-3W4ZIXB7-ChpBpV0k.js +89 -0
  518. pythinker_code/web/static/assets/katex-D2lIc1rk.css +1 -0
  519. pythinker_code/web/static/assets/kdl-DV7GczEv.js +1 -0
  520. pythinker_code/web/static/assets/kotlin-BdnUsdx6.js +1 -0
  521. pythinker_code/web/static/assets/kusto-DZf3V79B.js +1 -0
  522. pythinker_code/web/static/assets/laserwave-DUszq2jm.js +1 -0
  523. pythinker_code/web/static/assets/latex-B4uzh10-.js +1 -0
  524. pythinker_code/web/static/assets/layout-C3Jp1gKO.js +1 -0
  525. pythinker_code/web/static/assets/lean-BZvkOJ9d.js +1 -0
  526. pythinker_code/web/static/assets/less-B1dDrJ26.js +1 -0
  527. pythinker_code/web/static/assets/light-plus-B7mTdjB0.js +1 -0
  528. pythinker_code/web/static/assets/linear-BGHtL1N4.js +1 -0
  529. pythinker_code/web/static/assets/liquid-DYVedYrR.js +1 -0
  530. pythinker_code/web/static/assets/llvm-BtvRca6l.js +1 -0
  531. pythinker_code/web/static/assets/log-2UxHyX5q.js +1 -0
  532. pythinker_code/web/static/assets/logo-BtOb2qkB.js +1 -0
  533. pythinker_code/web/static/assets/lua-BbnMAYS6.js +1 -0
  534. pythinker_code/web/static/assets/luau-C-HG3fhB.js +1 -0
  535. pythinker_code/web/static/assets/make-CHLpvVh8.js +1 -0
  536. pythinker_code/web/static/assets/markdown-Cvjx9yec.js +1 -0
  537. pythinker_code/web/static/assets/marko-DZsq8hO1.js +1 -0
  538. pythinker_code/web/static/assets/material-theme-D5KoaKCx.js +1 -0
  539. pythinker_code/web/static/assets/material-theme-darker-BfHTSMKl.js +1 -0
  540. pythinker_code/web/static/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  541. pythinker_code/web/static/assets/material-theme-ocean-CyktbL80.js +1 -0
  542. pythinker_code/web/static/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  543. pythinker_code/web/static/assets/matlab-D7o27uSR.js +1 -0
  544. pythinker_code/web/static/assets/mdc-DUICxH0z.js +1 -0
  545. pythinker_code/web/static/assets/mdx-Cmh6b_Ma.js +1 -0
  546. pythinker_code/web/static/assets/mermaid-VLURNSYL-B2P5VJ9v.css +1 -0
  547. pythinker_code/web/static/assets/mermaid-VLURNSYL-C_HW6koB.js +465 -0
  548. pythinker_code/web/static/assets/mermaid-mWjccvbQ.js +1 -0
  549. pythinker_code/web/static/assets/mermaid.core-CnT4VrPC.js +191 -0
  550. pythinker_code/web/static/assets/min-Dn5VRVmX.js +1 -0
  551. pythinker_code/web/static/assets/min-dark-CafNBF8u.js +1 -0
  552. pythinker_code/web/static/assets/min-light-CTRr51gU.js +1 -0
  553. pythinker_code/web/static/assets/mindmap-definition-VGOIOE7T-x8EwhfIt.js +68 -0
  554. pythinker_code/web/static/assets/mipsasm-CKIfxQSi.js +1 -0
  555. pythinker_code/web/static/assets/mojo-B93PlW-d.js +1 -0
  556. pythinker_code/web/static/assets/monokai-D4h5O-jR.js +1 -0
  557. pythinker_code/web/static/assets/moonbit-Ba13S78F.js +1 -0
  558. pythinker_code/web/static/assets/move-Bu9oaDYs.js +1 -0
  559. pythinker_code/web/static/assets/narrat-DRg8JJMk.js +1 -0
  560. pythinker_code/web/static/assets/nextflow-BrzmwbiE.js +1 -0
  561. pythinker_code/web/static/assets/nginx-DknmC5AR.js +1 -0
  562. pythinker_code/web/static/assets/night-owl-C39BiMTA.js +1 -0
  563. pythinker_code/web/static/assets/nim-CVrawwO9.js +1 -0
  564. pythinker_code/web/static/assets/nix-CwoSXNpI.js +1 -0
  565. pythinker_code/web/static/assets/nord-Ddv68eIx.js +1 -0
  566. pythinker_code/web/static/assets/nushell-C-sUppwS.js +1 -0
  567. pythinker_code/web/static/assets/objective-c-DXmwc3jG.js +1 -0
  568. pythinker_code/web/static/assets/objective-cpp-CLxacb5B.js +1 -0
  569. pythinker_code/web/static/assets/ocaml-C0hk2d4L.js +1 -0
  570. pythinker_code/web/static/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  571. pythinker_code/web/static/assets/one-light-PoHY5YXO.js +1 -0
  572. pythinker_code/web/static/assets/openscad-C4EeE6gA.js +1 -0
  573. pythinker_code/web/static/assets/ordinal-Cboi1Yqb.js +1 -0
  574. pythinker_code/web/static/assets/pascal-D93ZcfNL.js +1 -0
  575. pythinker_code/web/static/assets/perl-C0TMdlhV.js +1 -0
  576. pythinker_code/web/static/assets/php-CDn_0X-4.js +1 -0
  577. pythinker_code/web/static/assets/pieDiagram-ADFJNKIX-DgxBKGz2.js +30 -0
  578. pythinker_code/web/static/assets/pkl-u5AG7uiY.js +1 -0
  579. pythinker_code/web/static/assets/plastic-3e1v2bzS.js +1 -0
  580. pythinker_code/web/static/assets/plsql-ChMvpjG-.js +1 -0
  581. pythinker_code/web/static/assets/po-BTJTHyun.js +1 -0
  582. pythinker_code/web/static/assets/poimandres-CS3Unz2-.js +1 -0
  583. pythinker_code/web/static/assets/polar-C0HS_06l.js +1 -0
  584. pythinker_code/web/static/assets/postcss-CXtECtnM.js +1 -0
  585. pythinker_code/web/static/assets/powerquery-CEu0bR-o.js +1 -0
  586. pythinker_code/web/static/assets/powershell-Dpen1YoG.js +1 -0
  587. pythinker_code/web/static/assets/prisma-Dd19v3D-.js +1 -0
  588. pythinker_code/web/static/assets/prolog-CbFg5uaA.js +1 -0
  589. pythinker_code/web/static/assets/proto-C7zT0LnQ.js +1 -0
  590. pythinker_code/web/static/assets/pug-CGlum2m_.js +1 -0
  591. pythinker_code/web/static/assets/puppet-BMWR74SV.js +1 -0
  592. pythinker_code/web/static/assets/purescript-CklMAg4u.js +1 -0
  593. pythinker_code/web/static/assets/python-B6aJPvgy.js +1 -0
  594. pythinker_code/web/static/assets/qml-3beO22l8.js +1 -0
  595. pythinker_code/web/static/assets/qmldir-C8lEn-DE.js +1 -0
  596. pythinker_code/web/static/assets/qss-IeuSbFQv.js +1 -0
  597. pythinker_code/web/static/assets/quadrantDiagram-AYHSOK5B-DSpe_dqk.js +7 -0
  598. pythinker_code/web/static/assets/r-Dspwwk_N.js +1 -0
  599. pythinker_code/web/static/assets/racket-BqYA7rlc.js +1 -0
  600. pythinker_code/web/static/assets/raku-DXvB9xmW.js +1 -0
  601. pythinker_code/web/static/assets/razor-C1TweQQi.js +1 -0
  602. pythinker_code/web/static/assets/red-bN70gL4F.js +1 -0
  603. pythinker_code/web/static/assets/reg-C-SQnVFl.js +1 -0
  604. pythinker_code/web/static/assets/regexp-CDVJQ6XC.js +1 -0
  605. pythinker_code/web/static/assets/rel-C3B-1QV4.js +1 -0
  606. pythinker_code/web/static/assets/requirementDiagram-UZGBJVZJ-8o9hozL-.js +64 -0
  607. pythinker_code/web/static/assets/riscv-BM1_JUlF.js +1 -0
  608. pythinker_code/web/static/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  609. pythinker_code/web/static/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  610. pythinker_code/web/static/assets/rose-pine-qdsjHGoJ.js +1 -0
  611. pythinker_code/web/static/assets/rosmsg-BJDFO7_C.js +1 -0
  612. pythinker_code/web/static/assets/rst-B0xPkSld.js +1 -0
  613. pythinker_code/web/static/assets/ruby-BvKwtOVI.js +1 -0
  614. pythinker_code/web/static/assets/rust-B1yitclQ.js +1 -0
  615. pythinker_code/web/static/assets/sankeyDiagram-TZEHDZUN-BLOSUixH.js +10 -0
  616. pythinker_code/web/static/assets/sas-cz2c8ADy.js +1 -0
  617. pythinker_code/web/static/assets/sass-Cj5Yp3dK.js +1 -0
  618. pythinker_code/web/static/assets/scala-C151Ov-r.js +1 -0
  619. pythinker_code/web/static/assets/scheme-C98Dy4si.js +1 -0
  620. pythinker_code/web/static/assets/scss-OYdSNvt2.js +1 -0
  621. pythinker_code/web/static/assets/sdbl-DVxCFoDh.js +1 -0
  622. pythinker_code/web/static/assets/sequenceDiagram-WL72ISMW-6F2G8JTU.js +145 -0
  623. pythinker_code/web/static/assets/shaderlab-Dg9Lc6iA.js +1 -0
  624. pythinker_code/web/static/assets/shellscript-Yzrsuije.js +1 -0
  625. pythinker_code/web/static/assets/shellsession-BADoaaVG.js +1 -0
  626. pythinker_code/web/static/assets/slack-dark-BthQWCQV.js +1 -0
  627. pythinker_code/web/static/assets/slack-ochin-DqwNpetd.js +1 -0
  628. pythinker_code/web/static/assets/smalltalk-BERRCDM3.js +1 -0
  629. pythinker_code/web/static/assets/snazzy-light-Bw305WKR.js +1 -0
  630. pythinker_code/web/static/assets/solarized-dark-DXbdFlpD.js +1 -0
  631. pythinker_code/web/static/assets/solarized-light-L9t79GZl.js +1 -0
  632. pythinker_code/web/static/assets/solidity-rGO070M0.js +1 -0
  633. pythinker_code/web/static/assets/soy-Brmx7dQM.js +1 -0
  634. pythinker_code/web/static/assets/sparql-rVzFXLq3.js +1 -0
  635. pythinker_code/web/static/assets/splunk-BtCnVYZw.js +1 -0
  636. pythinker_code/web/static/assets/sql-BLtJtn59.js +1 -0
  637. pythinker_code/web/static/assets/ssh-config-_ykCGR6B.js +1 -0
  638. pythinker_code/web/static/assets/stata-BH5u7GGu.js +1 -0
  639. pythinker_code/web/static/assets/stateDiagram-FKZM4ZOC-DP8xP0iJ.js +1 -0
  640. pythinker_code/web/static/assets/stateDiagram-v2-4FDKWEC3-1l6-EZNX.js +1 -0
  641. pythinker_code/web/static/assets/stylus-BEDo0Tqx.js +1 -0
  642. pythinker_code/web/static/assets/svelte-zxCyuUbr.js +1 -0
  643. pythinker_code/web/static/assets/swift-Dg5xB15N.js +1 -0
  644. pythinker_code/web/static/assets/synthwave-84-CbfX1IO0.js +1 -0
  645. pythinker_code/web/static/assets/system-verilog-CnnmHF94.js +1 -0
  646. pythinker_code/web/static/assets/systemd-4A_iFExJ.js +1 -0
  647. pythinker_code/web/static/assets/talonscript-CkByrt1z.js +1 -0
  648. pythinker_code/web/static/assets/tasl-QIJgUcNo.js +1 -0
  649. pythinker_code/web/static/assets/tcl-dwOrl1Do.js +1 -0
  650. pythinker_code/web/static/assets/templ-W15q3VgB.js +1 -0
  651. pythinker_code/web/static/assets/terraform-BETggiCN.js +1 -0
  652. pythinker_code/web/static/assets/tex-CvyZ59Mk.js +1 -0
  653. pythinker_code/web/static/assets/timeline-definition-IT6M3QCI-DMgruDfK.js +61 -0
  654. pythinker_code/web/static/assets/tokyo-night-hegEt444.js +1 -0
  655. pythinker_code/web/static/assets/toml-vGWfd6FD.js +1 -0
  656. pythinker_code/web/static/assets/treemap-KMMF4GRG-Bo9ZkrAK.js +128 -0
  657. pythinker_code/web/static/assets/ts-tags-zn1MmPIZ.js +1 -0
  658. pythinker_code/web/static/assets/tsv-B_m7g4N7.js +1 -0
  659. pythinker_code/web/static/assets/tsx-COt5Ahok.js +1 -0
  660. pythinker_code/web/static/assets/turtle-BsS91CYL.js +1 -0
  661. pythinker_code/web/static/assets/twig-CO9l9SDP.js +1 -0
  662. pythinker_code/web/static/assets/typescript-BPQ3VLAy.js +1 -0
  663. pythinker_code/web/static/assets/typespec-BGHnOYBU.js +1 -0
  664. pythinker_code/web/static/assets/typst-DHCkPAjA.js +1 -0
  665. pythinker_code/web/static/assets/v-BcVCzyr7.js +1 -0
  666. pythinker_code/web/static/assets/vala-CsfeWuGM.js +1 -0
  667. pythinker_code/web/static/assets/vb-D17OF-Vu.js +1 -0
  668. pythinker_code/web/static/assets/verilog-BQ8w6xss.js +1 -0
  669. pythinker_code/web/static/assets/vesper-DU1UobuO.js +1 -0
  670. pythinker_code/web/static/assets/vhdl-CeAyd5Ju.js +1 -0
  671. pythinker_code/web/static/assets/viml-CJc9bBzg.js +1 -0
  672. pythinker_code/web/static/assets/vitesse-black-Bkuqu6BP.js +1 -0
  673. pythinker_code/web/static/assets/vitesse-dark-D0r3Knsf.js +1 -0
  674. pythinker_code/web/static/assets/vitesse-light-CVO1_9PV.js +1 -0
  675. pythinker_code/web/static/assets/vue-DN_0RTcg.js +1 -0
  676. pythinker_code/web/static/assets/vue-html-AaS7Mt5G.js +1 -0
  677. pythinker_code/web/static/assets/vue-vine-CQOfvN7w.js +1 -0
  678. pythinker_code/web/static/assets/vyper-CDx5xZoG.js +1 -0
  679. pythinker_code/web/static/assets/wasm-CG6Dc4jp.js +1 -0
  680. pythinker_code/web/static/assets/wasm-MzD3tlZU.js +1 -0
  681. pythinker_code/web/static/assets/wenyan-BV7otONQ.js +1 -0
  682. pythinker_code/web/static/assets/wgsl-Dx-B1_4e.js +1 -0
  683. pythinker_code/web/static/assets/wikitext-BhOHFoWU.js +1 -0
  684. pythinker_code/web/static/assets/wit-5i3qLPDT.js +1 -0
  685. pythinker_code/web/static/assets/wolfram-lXgVvXCa.js +1 -0
  686. pythinker_code/web/static/assets/xml-sdJ4AIDG.js +1 -0
  687. pythinker_code/web/static/assets/xsl-CtQFsRM5.js +1 -0
  688. pythinker_code/web/static/assets/xychartDiagram-PRI3JC2R-GeF2johi.js +7 -0
  689. pythinker_code/web/static/assets/yaml-Buea-lGh.js +1 -0
  690. pythinker_code/web/static/assets/zenscript-DVFEvuxE.js +1 -0
  691. pythinker_code/web/static/assets/zig-VOosw3JB.js +1 -0
  692. pythinker_code/web/static/brand/apple-touch-icon.png +0 -0
  693. pythinker_code/web/static/brand/arctecture.webp +0 -0
  694. pythinker_code/web/static/brand/bimi-logo.svg +46 -0
  695. pythinker_code/web/static/brand/favicon.ico +0 -0
  696. pythinker_code/web/static/brand/fonts/dm-sans-latin-ext.woff2 +0 -0
  697. pythinker_code/web/static/brand/fonts/dm-sans-latin.woff2 +0 -0
  698. pythinker_code/web/static/brand/fonts/instrument-sans-latin-ext.woff2 +0 -0
  699. pythinker_code/web/static/brand/fonts/instrument-sans-latin.woff2 +0 -0
  700. pythinker_code/web/static/brand/fonts/instrument-serif-latin-ext.woff2 +0 -0
  701. pythinker_code/web/static/brand/fonts/instrument-serif-latin.woff2 +0 -0
  702. pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin-ext.woff2 +0 -0
  703. pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin.woff2 +0 -0
  704. pythinker_code/web/static/brand/fonts/libre-baskerville-latin-ext.woff2 +0 -0
  705. pythinker_code/web/static/brand/fonts/libre-baskerville-latin.woff2 +0 -0
  706. pythinker_code/web/static/brand/fonts/roboto-latin-ext.woff2 +0 -0
  707. pythinker_code/web/static/brand/fonts/roboto-latin.woff2 +0 -0
  708. pythinker_code/web/static/brand/icon-192.png +0 -0
  709. pythinker_code/web/static/brand/icon-512.png +0 -0
  710. pythinker_code/web/static/brand/icon.svg +1 -0
  711. pythinker_code/web/static/brand/logo.png +0 -0
  712. pythinker_code/web/static/brand/pythinker_animated.svg +79 -0
  713. pythinker_code/web/static/brand/robots.txt +4 -0
  714. pythinker_code/web/static/index.html +15 -0
  715. pythinker_code/web/static/logo.png +0 -0
  716. pythinker_code/web/store/__init__.py +1 -0
  717. pythinker_code/web/store/sessions.py +432 -0
  718. pythinker_code/wire/__init__.py +148 -0
  719. pythinker_code/wire/file.py +151 -0
  720. pythinker_code/wire/jsonrpc.py +263 -0
  721. pythinker_code/wire/protocol.py +2 -0
  722. pythinker_code/wire/root_hub.py +27 -0
  723. pythinker_code/wire/serde.py +26 -0
  724. pythinker_code/wire/server.py +1069 -0
  725. pythinker_code/wire/types.py +698 -0
  726. pythinker_code-2.0.0.dist-info/METADATA +660 -0
  727. pythinker_code-2.0.0.dist-info/RECORD +731 -0
  728. pythinker_code-2.0.0.dist-info/WHEEL +4 -0
  729. pythinker_code-2.0.0.dist-info/entry_points.txt +4 -0
  730. pythinker_code-2.0.0.dist-info/licenses/LICENSE +202 -0
  731. pythinker_code-2.0.0.dist-info/licenses/NOTICE +14 -0
@@ -0,0 +1,788 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import contextlib
5
+ import importlib
6
+ import inspect
7
+ import json
8
+ import time
9
+ from contextvars import ContextVar
10
+ from dataclasses import dataclass
11
+ from datetime import timedelta
12
+ from pathlib import Path
13
+ from typing import TYPE_CHECKING, Any, Literal, overload
14
+
15
+ from pythinker_core.tooling import (
16
+ CallableTool,
17
+ CallableTool2,
18
+ HandleResult,
19
+ Tool,
20
+ ToolError,
21
+ ToolOk,
22
+ Toolset,
23
+ )
24
+ from pythinker_core.tooling.error import (
25
+ ToolNotFoundError,
26
+ ToolParseError,
27
+ ToolRuntimeError,
28
+ )
29
+ from pythinker_core.tooling.mcp import convert_mcp_content
30
+ from pythinker_core.utils.typing import JsonType
31
+
32
+ from pythinker_code.exception import InvalidToolError, MCPRuntimeError
33
+ from pythinker_code.hooks.engine import HookEngine
34
+ from pythinker_code.tools import SkipThisTool
35
+ from pythinker_code.utils.logging import logger
36
+ from pythinker_code.wire.types import (
37
+ AudioURLPart,
38
+ ContentPart,
39
+ ImageURLPart,
40
+ MCPServerSnapshot,
41
+ MCPStatusSnapshot,
42
+ TextPart,
43
+ ToolCall,
44
+ ToolCallRequest,
45
+ ToolResult,
46
+ ToolReturnValue,
47
+ VideoURLPart,
48
+ )
49
+
50
+ if TYPE_CHECKING:
51
+ import fastmcp
52
+ import mcp
53
+ from fastmcp.client.client import CallToolResult
54
+ from fastmcp.client.transports import ClientTransport
55
+ from fastmcp.mcp_config import MCPConfig
56
+
57
+ from pythinker_code.soul.agent import Runtime
58
+
59
+ current_tool_call = ContextVar[ToolCall | None]("current_tool_call", default=None)
60
+
61
+ _current_session_id: ContextVar[str] = ContextVar("_current_session_id", default="")
62
+
63
+
64
+ def set_session_id(sid: str) -> None:
65
+ _current_session_id.set(sid)
66
+
67
+
68
+ def get_session_id() -> str:
69
+ return _current_session_id.get()
70
+
71
+
72
+ def _get_session_id() -> str:
73
+ return _current_session_id.get()
74
+
75
+
76
+ def get_current_tool_call_or_none() -> ToolCall | None:
77
+ """
78
+ Get the current tool call or None.
79
+ Expect to be not None when called from a `__call__` method of a tool.
80
+ """
81
+ return current_tool_call.get()
82
+
83
+
84
+ type ToolType = CallableTool | CallableTool2[Any]
85
+
86
+
87
+ if TYPE_CHECKING:
88
+
89
+ def type_check(pythinker_toolset: PythinkerToolset):
90
+ _: Toolset = pythinker_toolset
91
+
92
+
93
+ class PythinkerToolset:
94
+ def __init__(self) -> None:
95
+ self._tool_dict: dict[str, ToolType] = {}
96
+ self._hidden_tools: set[str] = set()
97
+ self._mcp_servers: dict[str, MCPServerInfo] = {}
98
+ self._mcp_loading_task: asyncio.Task[None] | None = None
99
+ self._deferred_mcp_load: tuple[list[MCPConfig], Runtime] | None = None
100
+ self._hook_engine: HookEngine = HookEngine()
101
+
102
+ def set_hook_engine(self, engine: HookEngine) -> None:
103
+ self._hook_engine = engine
104
+
105
+ def add(self, tool: ToolType) -> None:
106
+ self._tool_dict[tool.name] = tool
107
+
108
+ def hide(self, tool_name: str) -> bool:
109
+ """Hide a tool from the LLM tool list. Returns True if the tool exists."""
110
+ if tool_name in self._tool_dict:
111
+ self._hidden_tools.add(tool_name)
112
+ return True
113
+ return False
114
+
115
+ def unhide(self, tool_name: str) -> None:
116
+ """Restore a hidden tool to the LLM tool list."""
117
+ self._hidden_tools.discard(tool_name)
118
+
119
+ @overload
120
+ def find(self, tool_name_or_type: str) -> ToolType | None: ...
121
+ @overload
122
+ def find[T: ToolType](self, tool_name_or_type: type[T]) -> T | None: ...
123
+ def find(self, tool_name_or_type: str | type[ToolType]) -> ToolType | None:
124
+ if isinstance(tool_name_or_type, str):
125
+ return self._tool_dict.get(tool_name_or_type)
126
+ else:
127
+ for tool in self._tool_dict.values():
128
+ if isinstance(tool, tool_name_or_type):
129
+ return tool
130
+ return None
131
+
132
+ @property
133
+ def tools(self) -> list[Tool]:
134
+ return [
135
+ tool.base for tool in self._tool_dict.values() if tool.name not in self._hidden_tools
136
+ ]
137
+
138
+ def handle(self, tool_call: ToolCall) -> HandleResult:
139
+ token = current_tool_call.set(tool_call)
140
+ try:
141
+ if tool_call.function.name not in self._tool_dict:
142
+ return ToolResult(
143
+ tool_call_id=tool_call.id,
144
+ return_value=ToolNotFoundError(tool_call.function.name),
145
+ )
146
+
147
+ tool = self._tool_dict[tool_call.function.name]
148
+
149
+ try:
150
+ arguments: JsonType = json.loads(tool_call.function.arguments or "{}", strict=False)
151
+ except json.JSONDecodeError as e:
152
+ logger.warning(
153
+ "Tool call JSON parse error: {tool_name} (call_id={call_id}): {error}",
154
+ tool_name=tool_call.function.name,
155
+ call_id=tool_call.id,
156
+ error=e,
157
+ )
158
+ return ToolResult(tool_call_id=tool_call.id, return_value=ToolParseError(str(e)))
159
+
160
+ async def _call():
161
+ tool_input_dict = arguments if isinstance(arguments, dict) else {}
162
+
163
+ # --- PreToolUse ---
164
+ from pythinker_code.hooks import events
165
+
166
+ results = await self._hook_engine.trigger(
167
+ "PreToolUse",
168
+ matcher_value=tool_call.function.name,
169
+ input_data=events.pre_tool_use(
170
+ session_id=_get_session_id(),
171
+ cwd=str(Path.cwd()),
172
+ tool_name=tool_call.function.name,
173
+ tool_input=tool_input_dict,
174
+ tool_call_id=tool_call.id,
175
+ ),
176
+ )
177
+ for result in results:
178
+ if result.action == "block":
179
+ return ToolResult(
180
+ tool_call_id=tool_call.id,
181
+ return_value=ToolError(
182
+ message=result.reason or "Blocked by PreToolUse hook",
183
+ brief="Hook blocked",
184
+ ),
185
+ )
186
+
187
+ # --- Execute tool ---
188
+ from pythinker_code.telemetry import metrics as _m
189
+ from pythinker_code.telemetry import otel as _otel
190
+
191
+ t0 = time.monotonic()
192
+ _tool_span_cm = _otel.start_span(
193
+ "pythinker.tool",
194
+ {"tool.name": tool_call.function.name, "tool.call_id": tool_call.id},
195
+ )
196
+ _tool_span = _tool_span_cm.__enter__()
197
+ try:
198
+ ret = await tool.call(arguments)
199
+ except Exception as e:
200
+ tool_elapsed = time.monotonic() - t0
201
+ _tool_span.set_attribute("tool.success", False)
202
+ _tool_span.set_attribute("tool.error_type", type(e).__name__)
203
+ _tool_span.set_attribute("tool.duration_ms", int(tool_elapsed * 1000))
204
+ _tool_span_cm.__exit__(type(e), e, e.__traceback__)
205
+ _m.record_tool_call(
206
+ tool_name=tool_call.function.name,
207
+ duration_seconds=tool_elapsed,
208
+ success=False,
209
+ error_type=type(e).__name__,
210
+ )
211
+ _m.record_error(kind="tool_error", error_type=type(e).__name__)
212
+ logger.exception(
213
+ "Tool execution failed: {tool_name} (call_id={call_id})",
214
+ tool_name=tool_call.function.name,
215
+ call_id=tool_call.id,
216
+ )
217
+ # --- PostToolUseFailure (fire-and-forget) ---
218
+ _hook_task = asyncio.create_task(
219
+ self._hook_engine.trigger(
220
+ "PostToolUseFailure",
221
+ matcher_value=tool_call.function.name,
222
+ input_data=events.post_tool_use_failure(
223
+ session_id=_get_session_id(),
224
+ cwd=str(Path.cwd()),
225
+ tool_name=tool_call.function.name,
226
+ tool_input=tool_input_dict,
227
+ error=str(e),
228
+ tool_call_id=tool_call.id,
229
+ ),
230
+ )
231
+ )
232
+ _hook_task.add_done_callback(
233
+ lambda t: t.exception() if not t.cancelled() else None
234
+ )
235
+ from pythinker_code.telemetry import track
236
+
237
+ _error_type = type(e).__name__
238
+ track(
239
+ "tool_error",
240
+ tool_name=tool_call.function.name,
241
+ error_type=_error_type,
242
+ )
243
+ track(
244
+ "tool_call",
245
+ tool_name=tool_call.function.name,
246
+ success=False,
247
+ duration_ms=int(tool_elapsed * 1000),
248
+ error_type=_error_type,
249
+ )
250
+ return ToolResult(
251
+ tool_call_id=tool_call.id,
252
+ return_value=ToolRuntimeError(str(e)),
253
+ )
254
+
255
+ tool_elapsed = time.monotonic() - t0
256
+ _tool_succeeded = not isinstance(ret, ToolError)
257
+ _tool_span.set_attribute("tool.success", _tool_succeeded)
258
+ if isinstance(ret, ToolError):
259
+ _tool_span.set_attribute("tool.error_brief", ret.brief or "")
260
+ _tool_span.set_attribute("tool.duration_ms", int(tool_elapsed * 1000))
261
+ _tool_span_cm.__exit__(None, None, None)
262
+ _m.record_tool_call(
263
+ tool_name=tool_call.function.name,
264
+ duration_seconds=tool_elapsed,
265
+ success=_tool_succeeded,
266
+ )
267
+ logger.info(
268
+ "Tool {tool_name} completed in {elapsed:.1f}s (call_id={call_id})",
269
+ tool_name=tool_call.function.name,
270
+ elapsed=tool_elapsed,
271
+ call_id=tool_call.id,
272
+ )
273
+ from pythinker_code.telemetry import track as _track_tool_call
274
+
275
+ _track_tool_call(
276
+ "tool_call",
277
+ tool_name=tool_call.function.name,
278
+ success=not isinstance(ret, ToolError),
279
+ duration_ms=int(tool_elapsed * 1000),
280
+ )
281
+
282
+ # --- PostToolUse (fire-and-forget) ---
283
+ _hook_task = asyncio.create_task(
284
+ self._hook_engine.trigger(
285
+ "PostToolUse",
286
+ matcher_value=tool_call.function.name,
287
+ input_data=events.post_tool_use(
288
+ session_id=_get_session_id(),
289
+ cwd=str(Path.cwd()),
290
+ tool_name=tool_call.function.name,
291
+ tool_input=tool_input_dict,
292
+ tool_output=str(ret)[:2000],
293
+ tool_call_id=tool_call.id,
294
+ ),
295
+ )
296
+ )
297
+ _hook_task.add_done_callback(lambda t: t.exception() if not t.cancelled() else None)
298
+
299
+ return ToolResult(tool_call_id=tool_call.id, return_value=ret)
300
+
301
+ return asyncio.create_task(_call())
302
+ finally:
303
+ current_tool_call.reset(token)
304
+
305
+ def register_external_tool(
306
+ self,
307
+ name: str,
308
+ description: str,
309
+ parameters: dict[str, Any],
310
+ ) -> tuple[bool, str | None]:
311
+ if name in self._tool_dict:
312
+ existing = self._tool_dict[name]
313
+ if not isinstance(existing, WireExternalTool):
314
+ return False, "tool name conflicts with existing tool"
315
+ try:
316
+ tool = WireExternalTool(
317
+ name=name,
318
+ description=description,
319
+ parameters=parameters,
320
+ )
321
+ except Exception as e:
322
+ return False, str(e)
323
+ self.add(tool)
324
+ return True, None
325
+
326
+ @property
327
+ def mcp_servers(self) -> dict[str, MCPServerInfo]:
328
+ """Get MCP servers info."""
329
+ return self._mcp_servers
330
+
331
+ def mcp_status_snapshot(self) -> MCPStatusSnapshot | None:
332
+ """Return a read-only snapshot of current MCP startup state."""
333
+ if not self._mcp_servers:
334
+ return None
335
+
336
+ servers = tuple(
337
+ MCPServerSnapshot(
338
+ name=name,
339
+ status=info.status,
340
+ tools=tuple(tool.name for tool in info.tools),
341
+ )
342
+ for name, info in self._mcp_servers.items()
343
+ )
344
+ return MCPStatusSnapshot(
345
+ loading=self.has_pending_mcp_tools(),
346
+ connected=sum(1 for server in servers if server.status == "connected"),
347
+ total=len(servers),
348
+ tools=sum(len(server.tools) for server in servers),
349
+ servers=servers,
350
+ )
351
+
352
+ def defer_mcp_tool_loading(self, mcp_configs: list[MCPConfig], runtime: Runtime) -> None:
353
+ """Store MCP configs for a later background startup."""
354
+ self._deferred_mcp_load = (list(mcp_configs), runtime)
355
+
356
+ def has_deferred_mcp_tools(self) -> bool:
357
+ """Return True when MCP loading is configured but has not started yet."""
358
+ return self._deferred_mcp_load is not None
359
+
360
+ async def start_deferred_mcp_tool_loading(self) -> bool:
361
+ """Start any deferred MCP loading in the background."""
362
+ if self._deferred_mcp_load is None:
363
+ return False
364
+ if self._mcp_loading_task is not None or self._mcp_servers:
365
+ self._deferred_mcp_load = None
366
+ return False
367
+
368
+ mcp_configs, runtime = self._deferred_mcp_load
369
+ self._deferred_mcp_load = None
370
+ await self.load_mcp_tools(mcp_configs, runtime, in_background=True)
371
+ return True
372
+
373
+ def load_tools(self, tool_paths: list[str], dependencies: dict[type[Any], Any]) -> None:
374
+ """
375
+ Load tools from paths like `pythinker_code.tools.shell:Shell`.
376
+
377
+ Raises:
378
+ InvalidToolError(PythinkerCLIException, ValueError): When any tool cannot be loaded.
379
+ """
380
+
381
+ good_tools: list[str] = []
382
+ bad_tools: list[str] = []
383
+
384
+ for tool_path in tool_paths:
385
+ try:
386
+ tool = self._load_tool(tool_path, dependencies)
387
+ except SkipThisTool:
388
+ logger.info("Skipping tool: {tool_path}", tool_path=tool_path)
389
+ continue
390
+ if tool:
391
+ self.add(tool)
392
+ good_tools.append(tool_path)
393
+ else:
394
+ bad_tools.append(tool_path)
395
+ logger.info("Loaded tools: {good_tools}", good_tools=good_tools)
396
+ if bad_tools:
397
+ raise InvalidToolError(f"Invalid tools: {bad_tools}")
398
+
399
+ @staticmethod
400
+ def _load_tool(tool_path: str, dependencies: dict[type[Any], Any]) -> ToolType | None:
401
+ logger.debug("Loading tool: {tool_path}", tool_path=tool_path)
402
+ module_name, class_name = tool_path.rsplit(":", 1)
403
+ try:
404
+ module = importlib.import_module(module_name)
405
+ except ImportError as e:
406
+ logger.warning(
407
+ "Tool module import failed: {module_name}: {error}",
408
+ module_name=module_name,
409
+ error=e,
410
+ )
411
+ return None
412
+ tool_cls = getattr(module, class_name, None)
413
+ if tool_cls is None:
414
+ logger.warning(
415
+ "Tool class not found: {class_name} in {module_name}",
416
+ class_name=class_name,
417
+ module_name=module_name,
418
+ )
419
+ return None
420
+ args: list[Any] = []
421
+ if "__init__" in tool_cls.__dict__:
422
+ # the tool class overrides the `__init__` of base class
423
+ for param in inspect.signature(tool_cls).parameters.values():
424
+ if param.kind == inspect.Parameter.KEYWORD_ONLY:
425
+ # once we encounter a keyword-only parameter, we stop injecting dependencies
426
+ break
427
+ # all positional parameters should be dependencies to be injected
428
+ if param.annotation not in dependencies:
429
+ raise ValueError(f"Tool dependency not found: {param.annotation}")
430
+ args.append(dependencies[param.annotation])
431
+ return tool_cls(*args)
432
+
433
+ # TODO(rc): remove `in_background` parameter and always load in background
434
+ async def load_mcp_tools(
435
+ self, mcp_configs: list[MCPConfig], runtime: Runtime, in_background: bool = True
436
+ ) -> None:
437
+ """
438
+ Load MCP tools from specified MCP configs.
439
+
440
+ Raises:
441
+ MCPRuntimeError(PythinkerCLIException, RuntimeError): When any MCP server cannot be
442
+ connected.
443
+ """
444
+ import fastmcp
445
+ from fastmcp.mcp_config import MCPConfig, RemoteMCPServer
446
+
447
+ from pythinker_code.ui.shell.prompt import toast
448
+
449
+ async def _check_oauth_tokens(server_url: str) -> bool:
450
+ """Check if OAuth tokens exist for the server."""
451
+ try:
452
+ from fastmcp.client.auth.oauth import FileTokenStorage
453
+
454
+ storage = FileTokenStorage(server_url=server_url)
455
+ tokens = await storage.get_tokens()
456
+ return tokens is not None
457
+ except Exception:
458
+ return False
459
+
460
+ def _toast_mcp(message: str) -> None:
461
+ if in_background:
462
+ toast(
463
+ message,
464
+ duration=10.0,
465
+ topic="mcp",
466
+ immediate=True,
467
+ position="right",
468
+ )
469
+
470
+ oauth_servers: dict[str, str] = {}
471
+
472
+ async def _connect_server(
473
+ server_name: str, server_info: MCPServerInfo
474
+ ) -> tuple[str, Exception | None]:
475
+ if server_info.status != "pending":
476
+ return server_name, None
477
+
478
+ server_info.status = "connecting"
479
+ try:
480
+ async with server_info.client as client:
481
+ for tool in await client.list_tools():
482
+ server_info.tools.append(
483
+ MCPTool(server_name, tool, client, runtime=runtime)
484
+ )
485
+
486
+ for tool in server_info.tools:
487
+ self.add(tool)
488
+
489
+ server_info.status = "connected"
490
+ logger.info("Connected MCP server: {server_name}", server_name=server_name)
491
+ return server_name, None
492
+ except Exception as e:
493
+ logger.error(
494
+ "Failed to connect MCP server: {server_name}, error: {error}",
495
+ server_name=server_name,
496
+ error=e,
497
+ )
498
+ server_info.status = "failed"
499
+ return server_name, e
500
+
501
+ async def _connect():
502
+ _toast_mcp("connecting to mcp servers...")
503
+ unauthorized_servers: dict[str, str] = {}
504
+ for server_name, server_info in self._mcp_servers.items():
505
+ server_url = oauth_servers.get(server_name)
506
+ if not server_url:
507
+ continue
508
+ if not await _check_oauth_tokens(server_url):
509
+ logger.warning(
510
+ "Skipping OAuth MCP server '{server_name}': not authorized. "
511
+ "Run 'pythinker mcp auth {server_name}' first.",
512
+ server_name=server_name,
513
+ )
514
+ server_info.status = "unauthorized"
515
+ unauthorized_servers[server_name] = server_url
516
+
517
+ tasks = [
518
+ asyncio.create_task(_connect_server(server_name, server_info))
519
+ for server_name, server_info in self._mcp_servers.items()
520
+ if server_info.status == "pending"
521
+ ]
522
+ results = await asyncio.gather(*tasks) if tasks else []
523
+ failed_servers = {name: error for name, error in results if error is not None}
524
+
525
+ for mcp_config in mcp_configs:
526
+ # Skip empty MCP configs (no servers defined)
527
+ if not mcp_config.mcpServers:
528
+ logger.debug("Skipping empty MCP config: {mcp_config}", mcp_config=mcp_config)
529
+ continue
530
+
531
+ if failed_servers:
532
+ _toast_mcp("mcp connection failed")
533
+ raise MCPRuntimeError(f"Failed to connect MCP servers: {failed_servers}")
534
+ if unauthorized_servers:
535
+ _toast_mcp("mcp authorization needed")
536
+ else:
537
+ _toast_mcp("mcp servers connected")
538
+
539
+ for mcp_config in mcp_configs:
540
+ if not mcp_config.mcpServers:
541
+ logger.debug("Skipping empty MCP config: {mcp_config}", mcp_config=mcp_config)
542
+ continue
543
+
544
+ for server_name, server_config in mcp_config.mcpServers.items():
545
+ if isinstance(server_config, RemoteMCPServer) and server_config.auth == "oauth":
546
+ oauth_servers[server_name] = server_config.url
547
+
548
+ client = fastmcp.Client(MCPConfig(mcpServers={server_name: server_config}))
549
+ self._mcp_servers[server_name] = MCPServerInfo(
550
+ status="pending", client=client, tools=[]
551
+ )
552
+
553
+ if in_background:
554
+ self._mcp_loading_task = asyncio.create_task(_connect())
555
+ else:
556
+ await _connect()
557
+
558
+ def has_pending_mcp_tools(self) -> bool:
559
+ """Return True if the background MCP tool-loading task is still running."""
560
+ return self._mcp_loading_task is not None and not self._mcp_loading_task.done()
561
+
562
+ async def wait_for_mcp_tools(self) -> None:
563
+ """Wait for background MCP tool loading to finish."""
564
+ task = self._mcp_loading_task
565
+ if not task:
566
+ return
567
+ try:
568
+ await task
569
+ finally:
570
+ if self._mcp_loading_task is task and task.done():
571
+ self._mcp_loading_task = None
572
+
573
+ async def cleanup(self) -> None:
574
+ """Cleanup any resources held by the toolset."""
575
+ self._deferred_mcp_load = None
576
+ if self._mcp_loading_task:
577
+ self._mcp_loading_task.cancel()
578
+ with contextlib.suppress(asyncio.CancelledError):
579
+ await self._mcp_loading_task
580
+ for server_info in self._mcp_servers.values():
581
+ await server_info.client.close()
582
+
583
+
584
+ @dataclass(slots=True)
585
+ class MCPServerInfo:
586
+ status: Literal["pending", "connecting", "connected", "failed", "unauthorized"]
587
+ client: fastmcp.Client[Any]
588
+ tools: list[MCPTool[Any]]
589
+
590
+
591
+ class MCPTool[T: ClientTransport](CallableTool):
592
+ def __init__(
593
+ self,
594
+ server_name: str,
595
+ mcp_tool: mcp.Tool,
596
+ client: fastmcp.Client[T],
597
+ *,
598
+ runtime: Runtime,
599
+ **kwargs: Any,
600
+ ):
601
+ super().__init__(
602
+ name=mcp_tool.name,
603
+ description=(
604
+ f"This is an MCP (Model Context Protocol) tool from MCP server `{server_name}`.\n\n"
605
+ f"{mcp_tool.description or 'No description provided.'}"
606
+ ),
607
+ parameters=mcp_tool.inputSchema,
608
+ **kwargs,
609
+ )
610
+ self._mcp_tool = mcp_tool
611
+ self._client = client
612
+ self._runtime = runtime
613
+ self._timeout = timedelta(milliseconds=runtime.config.mcp.client.tool_call_timeout_ms)
614
+ self._action_name = f"mcp:{mcp_tool.name}"
615
+
616
+ async def __call__(self, *args: Any, **kwargs: Any) -> ToolReturnValue:
617
+ description = f"Call MCP tool `{self._mcp_tool.name}`."
618
+ result = await self._runtime.approval.request(self.name, self._action_name, description)
619
+ if not result:
620
+ return result.rejection_error()
621
+
622
+ try:
623
+ async with self._client as client:
624
+ result = await client.call_tool(
625
+ self._mcp_tool.name,
626
+ kwargs,
627
+ timeout=self._timeout,
628
+ raise_on_error=False,
629
+ )
630
+ if result.is_error:
631
+ logger.warning(
632
+ "MCP tool returned error: {tool_name}: {content}",
633
+ tool_name=self._mcp_tool.name,
634
+ content=[str(p) for p in result.content][:3],
635
+ )
636
+ return convert_mcp_tool_result(result)
637
+ except Exception as e:
638
+ # fastmcp raises `RuntimeError` on timeout and we cannot tell it from other errors
639
+ exc_msg = str(e).lower()
640
+ if "timeout" in exc_msg or "timed out" in exc_msg:
641
+ logger.warning(
642
+ "MCP tool call timed out: {tool_name}: {error}",
643
+ tool_name=self._mcp_tool.name,
644
+ error=e,
645
+ )
646
+ return ToolError(
647
+ message=(
648
+ f"Timeout while calling MCP tool `{self._mcp_tool.name}`. "
649
+ "You may explain to the user that the timeout config is set too low."
650
+ ),
651
+ brief="Timeout",
652
+ )
653
+ logger.error(
654
+ "MCP tool call failed: {tool_name}: {error}",
655
+ tool_name=self._mcp_tool.name,
656
+ error=e,
657
+ )
658
+ raise
659
+
660
+
661
+ class WireExternalTool(CallableTool):
662
+ def __init__(self, *, name: str, description: str, parameters: dict[str, Any]) -> None:
663
+ super().__init__(
664
+ name=name,
665
+ description=description or "No description provided.",
666
+ parameters=parameters,
667
+ )
668
+
669
+ async def __call__(self, *args: Any, **kwargs: Any) -> ToolReturnValue:
670
+ tool_call = get_current_tool_call_or_none()
671
+ if tool_call is None:
672
+ return ToolError(
673
+ message="External tool calls must be invoked from a tool call context.",
674
+ brief="Invalid tool call",
675
+ )
676
+
677
+ from pythinker_code.soul import get_wire_or_none
678
+
679
+ wire = get_wire_or_none()
680
+ if wire is None:
681
+ logger.error(
682
+ "Wire is not available for external tool call: {tool_name}", tool_name=self.name
683
+ )
684
+ return ToolError(
685
+ message="Wire is not available for external tool calls.",
686
+ brief="Wire unavailable",
687
+ )
688
+
689
+ external_tool_call = ToolCallRequest.from_tool_call(tool_call)
690
+ wire.soul_side.send(external_tool_call)
691
+ try:
692
+ return await external_tool_call.wait()
693
+ except asyncio.CancelledError:
694
+ raise
695
+ except Exception as e:
696
+ logger.exception("External tool call failed: {tool_name}:", tool_name=self.name)
697
+ return ToolError(
698
+ message=f"External tool call failed: {e}",
699
+ brief="External tool error",
700
+ )
701
+
702
+
703
+ # Maximum characters allowed in MCP tool output before truncation.
704
+ # Built-in tools use 50K via ToolResultBuilder; MCP gets a wider budget because
705
+ # multi-part results (e.g. text + image) are common, but still needs a cap to
706
+ # prevent context overflow from tools like Playwright that return full DOMs.
707
+ MCP_MAX_OUTPUT_CHARS = 100_000
708
+
709
+
710
+ def _media_part_size(part: ContentPart) -> int | None:
711
+ """Return the payload size of a media part, or ``None`` for non-media parts."""
712
+ if isinstance(part, ImageURLPart):
713
+ return len(part.image_url.url)
714
+ if isinstance(part, AudioURLPart):
715
+ return len(part.audio_url.url)
716
+ if isinstance(part, VideoURLPart):
717
+ return len(part.video_url.url)
718
+ return None
719
+
720
+
721
+ def convert_mcp_tool_result(result: CallToolResult) -> ToolReturnValue:
722
+ """Convert MCP tool result to Pythinker Core tool return value.
723
+
724
+ All content — text *and* inline media (``data:`` URLs) — is subject to
725
+ a shared *MCP_MAX_OUTPUT_CHARS* character budget. Text parts are
726
+ truncated in-place; media parts that exceed the remaining budget are
727
+ dropped and replaced with a descriptive placeholder.
728
+
729
+ Unsupported content types are caught and replaced with a ``TextPart``
730
+ placeholder instead of crashing the turn.
731
+ """
732
+ content: list[ContentPart] = []
733
+ char_budget = MCP_MAX_OUTPUT_CHARS
734
+ truncated = False
735
+
736
+ for part in result.content:
737
+ try:
738
+ converted = convert_mcp_content(part)
739
+ except ValueError as exc:
740
+ logger.warning(
741
+ "Skipping unsupported MCP content part: {error}",
742
+ error=exc,
743
+ )
744
+ converted = TextPart(text=f"[Unsupported content: {exc}]")
745
+
746
+ # --- budget enforcement (text) ---
747
+ if isinstance(converted, TextPart):
748
+ if char_budget <= 0:
749
+ truncated = True
750
+ continue
751
+ if len(converted.text) > char_budget:
752
+ converted = TextPart(text=converted.text[:char_budget])
753
+ truncated = True
754
+ char_budget -= len(converted.text)
755
+ content.append(converted)
756
+ continue
757
+
758
+ # --- budget enforcement (media: image / audio / video) ---
759
+ media_size = _media_part_size(converted)
760
+ if media_size is not None:
761
+ if media_size > char_budget:
762
+ truncated = True
763
+ continue # drop the oversized media part silently
764
+ char_budget -= media_size
765
+ content.append(converted)
766
+ continue
767
+
768
+ # Unknown ContentPart subclass — pass through without budget impact
769
+ content.append(converted)
770
+
771
+ if truncated:
772
+ content.append(
773
+ TextPart(
774
+ text=(
775
+ f"\n\n[Output truncated: exceeded {MCP_MAX_OUTPUT_CHARS} character limit. "
776
+ "Use pagination or more specific queries to get remaining content.]"
777
+ )
778
+ )
779
+ )
780
+
781
+ if result.is_error:
782
+ return ToolError(
783
+ output=content,
784
+ message="Tool returned an error. The output may be error message or incomplete output",
785
+ brief="",
786
+ )
787
+ else:
788
+ return ToolOk(output=content)