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