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.
- pythinker_code/CHANGELOG.md +16 -0
- pythinker_code/__init__.py +0 -0
- pythinker_code/__main__.py +92 -0
- pythinker_code/acp/AGENTS.md +93 -0
- pythinker_code/acp/__init__.py +13 -0
- pythinker_code/acp/convert.py +128 -0
- pythinker_code/acp/host.py +298 -0
- pythinker_code/acp/mcp.py +46 -0
- pythinker_code/acp/server.py +497 -0
- pythinker_code/acp/session.py +496 -0
- pythinker_code/acp/tools.py +167 -0
- pythinker_code/acp/types.py +13 -0
- pythinker_code/acp/version.py +45 -0
- pythinker_code/agents/default/agent.yaml +36 -0
- pythinker_code/agents/default/coder.yaml +25 -0
- pythinker_code/agents/default/explore.yaml +46 -0
- pythinker_code/agents/default/plan.yaml +30 -0
- pythinker_code/agents/default/system.md +164 -0
- pythinker_code/agents/okabe/agent.yaml +22 -0
- pythinker_code/agentspec.py +163 -0
- pythinker_code/app.py +820 -0
- pythinker_code/approval_runtime/__init__.py +29 -0
- pythinker_code/approval_runtime/models.py +42 -0
- pythinker_code/approval_runtime/runtime.py +235 -0
- pythinker_code/auth/__init__.py +25 -0
- pythinker_code/auth/anthropic_direct.py +207 -0
- pythinker_code/auth/deepseek.py +192 -0
- pythinker_code/auth/lm_studio.py +418 -0
- pythinker_code/auth/minimax.py +203 -0
- pythinker_code/auth/oauth.py +1122 -0
- pythinker_code/auth/ollama.py +293 -0
- pythinker_code/auth/openai.py +771 -0
- pythinker_code/auth/opencode_go.py +203 -0
- pythinker_code/auth/openrouter.py +225 -0
- pythinker_code/auth/platforms.py +466 -0
- pythinker_code/background/__init__.py +36 -0
- pythinker_code/background/agent_runner.py +231 -0
- pythinker_code/background/ids.py +19 -0
- pythinker_code/background/manager.py +650 -0
- pythinker_code/background/models.py +105 -0
- pythinker_code/background/store.py +237 -0
- pythinker_code/background/summary.py +66 -0
- pythinker_code/background/worker.py +209 -0
- pythinker_code/cli/__init__.py +1326 -0
- pythinker_code/cli/__main__.py +19 -0
- pythinker_code/cli/_lazy_group.py +238 -0
- pythinker_code/cli/export.py +322 -0
- pythinker_code/cli/info.py +62 -0
- pythinker_code/cli/mcp.py +349 -0
- pythinker_code/cli/plugin.py +351 -0
- pythinker_code/cli/toad.py +74 -0
- pythinker_code/cli/vis.py +38 -0
- pythinker_code/cli/web.py +80 -0
- pythinker_code/config.py +453 -0
- pythinker_code/constant.py +33 -0
- pythinker_code/exception.py +43 -0
- pythinker_code/hooks/__init__.py +4 -0
- pythinker_code/hooks/config.py +34 -0
- pythinker_code/hooks/engine.py +371 -0
- pythinker_code/hooks/events.py +190 -0
- pythinker_code/hooks/runner.py +89 -0
- pythinker_code/llm.py +412 -0
- pythinker_code/metadata.py +79 -0
- pythinker_code/notifications/__init__.py +33 -0
- pythinker_code/notifications/llm.py +77 -0
- pythinker_code/notifications/manager.py +145 -0
- pythinker_code/notifications/models.py +50 -0
- pythinker_code/notifications/notifier.py +41 -0
- pythinker_code/notifications/store.py +118 -0
- pythinker_code/notifications/wire.py +21 -0
- pythinker_code/plugin/__init__.py +124 -0
- pythinker_code/plugin/manager.py +153 -0
- pythinker_code/plugin/tool.py +173 -0
- pythinker_code/prompts/__init__.py +6 -0
- pythinker_code/prompts/compact.md +73 -0
- pythinker_code/prompts/init.md +21 -0
- pythinker_code/py.typed +0 -0
- pythinker_code/session.py +319 -0
- pythinker_code/session_fork.py +325 -0
- pythinker_code/session_state.py +132 -0
- pythinker_code/share.py +14 -0
- pythinker_code/skill/__init__.py +727 -0
- pythinker_code/skill/flow/__init__.py +99 -0
- pythinker_code/skill/flow/d2.py +482 -0
- pythinker_code/skill/flow/mermaid.py +266 -0
- pythinker_code/skills/pythinker-code-help/SKILL.md +54 -0
- pythinker_code/skills/skill-creator/SKILL.md +367 -0
- pythinker_code/soul/__init__.py +304 -0
- pythinker_code/soul/agent.py +520 -0
- pythinker_code/soul/approval.py +267 -0
- pythinker_code/soul/btw.py +214 -0
- pythinker_code/soul/compaction.py +189 -0
- pythinker_code/soul/context.py +339 -0
- pythinker_code/soul/denwarenji.py +39 -0
- pythinker_code/soul/dynamic_injection.py +84 -0
- pythinker_code/soul/dynamic_injections/__init__.py +0 -0
- pythinker_code/soul/dynamic_injections/auto_mode.py +72 -0
- pythinker_code/soul/dynamic_injections/plan_mode.py +239 -0
- pythinker_code/soul/message.py +92 -0
- pythinker_code/soul/pythinkersoul.py +1613 -0
- pythinker_code/soul/slash.py +340 -0
- pythinker_code/soul/toolset.py +788 -0
- pythinker_code/subagents/__init__.py +21 -0
- pythinker_code/subagents/builder.py +42 -0
- pythinker_code/subagents/core.py +86 -0
- pythinker_code/subagents/git_context.py +172 -0
- pythinker_code/subagents/models.py +54 -0
- pythinker_code/subagents/output.py +71 -0
- pythinker_code/subagents/registry.py +28 -0
- pythinker_code/subagents/runner.py +428 -0
- pythinker_code/subagents/store.py +196 -0
- pythinker_code/telemetry/__init__.py +211 -0
- pythinker_code/telemetry/config.py +54 -0
- pythinker_code/telemetry/crash.py +157 -0
- pythinker_code/telemetry/metrics.py +208 -0
- pythinker_code/telemetry/otel.py +240 -0
- pythinker_code/telemetry/sentry.py +167 -0
- pythinker_code/telemetry/sink.py +189 -0
- pythinker_code/tools/AGENTS.md +6 -0
- pythinker_code/tools/__init__.py +105 -0
- pythinker_code/tools/agent/__init__.py +277 -0
- pythinker_code/tools/agent/description.md +41 -0
- pythinker_code/tools/ask_user/__init__.py +159 -0
- pythinker_code/tools/ask_user/description.md +19 -0
- pythinker_code/tools/background/__init__.py +318 -0
- pythinker_code/tools/background/list.md +10 -0
- pythinker_code/tools/background/output.md +11 -0
- pythinker_code/tools/background/stop.md +8 -0
- pythinker_code/tools/display.py +46 -0
- pythinker_code/tools/dmail/__init__.py +38 -0
- pythinker_code/tools/dmail/dmail.md +17 -0
- pythinker_code/tools/file/__init__.py +30 -0
- pythinker_code/tools/file/glob.md +17 -0
- pythinker_code/tools/file/glob.py +160 -0
- pythinker_code/tools/file/grep.md +6 -0
- pythinker_code/tools/file/grep_local.py +589 -0
- pythinker_code/tools/file/plan_mode.py +45 -0
- pythinker_code/tools/file/read.md +16 -0
- pythinker_code/tools/file/read.py +300 -0
- pythinker_code/tools/file/read_media.md +24 -0
- pythinker_code/tools/file/read_media.py +217 -0
- pythinker_code/tools/file/replace.md +7 -0
- pythinker_code/tools/file/replace.py +195 -0
- pythinker_code/tools/file/utils.py +257 -0
- pythinker_code/tools/file/write.md +5 -0
- pythinker_code/tools/file/write.py +177 -0
- pythinker_code/tools/plan/__init__.py +327 -0
- pythinker_code/tools/plan/description.md +29 -0
- pythinker_code/tools/plan/enter.py +190 -0
- pythinker_code/tools/plan/enter_description.md +35 -0
- pythinker_code/tools/plan/heroes.py +277 -0
- pythinker_code/tools/shell/__init__.py +253 -0
- pythinker_code/tools/shell/bash.md +35 -0
- pythinker_code/tools/shell/powershell.md +30 -0
- pythinker_code/tools/test.py +55 -0
- pythinker_code/tools/think/__init__.py +21 -0
- pythinker_code/tools/think/think.md +1 -0
- pythinker_code/tools/todo/__init__.py +168 -0
- pythinker_code/tools/todo/set_todo_list.md +23 -0
- pythinker_code/tools/utils.py +199 -0
- pythinker_code/tools/web/__init__.py +4 -0
- pythinker_code/tools/web/fetch.md +1 -0
- pythinker_code/tools/web/fetch.py +189 -0
- pythinker_code/tools/web/search.md +1 -0
- pythinker_code/tools/web/search.py +163 -0
- pythinker_code/ui/__init__.py +0 -0
- pythinker_code/ui/acp/__init__.py +99 -0
- pythinker_code/ui/print/__init__.py +474 -0
- pythinker_code/ui/print/visualize.py +185 -0
- pythinker_code/ui/shell/__init__.py +1696 -0
- pythinker_code/ui/shell/console.py +109 -0
- pythinker_code/ui/shell/debug.py +190 -0
- pythinker_code/ui/shell/echo.py +17 -0
- pythinker_code/ui/shell/export_import.py +117 -0
- pythinker_code/ui/shell/keyboard.py +300 -0
- pythinker_code/ui/shell/mcp_status.py +113 -0
- pythinker_code/ui/shell/model_picker.py +318 -0
- pythinker_code/ui/shell/oauth.py +272 -0
- pythinker_code/ui/shell/placeholders.py +531 -0
- pythinker_code/ui/shell/prompt.py +2278 -0
- pythinker_code/ui/shell/replay.py +215 -0
- pythinker_code/ui/shell/session_picker.py +227 -0
- pythinker_code/ui/shell/setup.py +212 -0
- pythinker_code/ui/shell/slash.py +898 -0
- pythinker_code/ui/shell/startup.py +32 -0
- pythinker_code/ui/shell/task_browser.py +486 -0
- pythinker_code/ui/shell/update.py +350 -0
- pythinker_code/ui/shell/usage.py +291 -0
- pythinker_code/ui/shell/usage_adapters/__init__.py +50 -0
- pythinker_code/ui/shell/usage_adapters/anthropic_admin.py +233 -0
- pythinker_code/ui/shell/usage_adapters/base.py +72 -0
- pythinker_code/ui/shell/usage_adapters/deepseek.py +137 -0
- pythinker_code/ui/shell/usage_adapters/minimax.py +236 -0
- pythinker_code/ui/shell/usage_adapters/openai_admin.py +225 -0
- pythinker_code/ui/shell/usage_adapters/openai_chatgpt.py +241 -0
- pythinker_code/ui/shell/usage_adapters/opencode_go.py +232 -0
- pythinker_code/ui/shell/usage_adapters/openrouter.py +105 -0
- pythinker_code/ui/shell/usage_adapters/pythinker.py +189 -0
- pythinker_code/ui/shell/usage_adapters/pythinker_ai.py +50 -0
- pythinker_code/ui/shell/usage_render.py +150 -0
- pythinker_code/ui/shell/visualize/__init__.py +165 -0
- pythinker_code/ui/shell/visualize/_approval_panel.py +505 -0
- pythinker_code/ui/shell/visualize/_blocks.py +629 -0
- pythinker_code/ui/shell/visualize/_btw_panel.py +224 -0
- pythinker_code/ui/shell/visualize/_input_router.py +48 -0
- pythinker_code/ui/shell/visualize/_interactive.py +523 -0
- pythinker_code/ui/shell/visualize/_live_view.py +826 -0
- pythinker_code/ui/shell/visualize/_question_panel.py +586 -0
- pythinker_code/ui/theme.py +241 -0
- pythinker_code/usage_ratelimit_cache.py +175 -0
- pythinker_code/utils/__init__.py +0 -0
- pythinker_code/utils/aiohttp.py +24 -0
- pythinker_code/utils/aioqueue.py +72 -0
- pythinker_code/utils/broadcast.py +37 -0
- pythinker_code/utils/changelog.py +108 -0
- pythinker_code/utils/clipboard.py +246 -0
- pythinker_code/utils/datetime.py +64 -0
- pythinker_code/utils/diff.py +135 -0
- pythinker_code/utils/editor.py +91 -0
- pythinker_code/utils/environment.py +73 -0
- pythinker_code/utils/envvar.py +22 -0
- pythinker_code/utils/export.py +696 -0
- pythinker_code/utils/file_filter.py +375 -0
- pythinker_code/utils/frontmatter.py +70 -0
- pythinker_code/utils/io.py +27 -0
- pythinker_code/utils/logging.py +146 -0
- pythinker_code/utils/media_tags.py +29 -0
- pythinker_code/utils/message.py +24 -0
- pythinker_code/utils/path.py +199 -0
- pythinker_code/utils/proctitle.py +33 -0
- pythinker_code/utils/proxy.py +31 -0
- pythinker_code/utils/pyinstaller.py +45 -0
- pythinker_code/utils/rich/__init__.py +33 -0
- pythinker_code/utils/rich/columns.py +99 -0
- pythinker_code/utils/rich/diff_render.py +481 -0
- pythinker_code/utils/rich/markdown.py +900 -0
- pythinker_code/utils/rich/markdown_sample.md +108 -0
- pythinker_code/utils/rich/markdown_sample_short.md +2 -0
- pythinker_code/utils/rich/syntax.py +114 -0
- pythinker_code/utils/sensitive.py +54 -0
- pythinker_code/utils/server.py +121 -0
- pythinker_code/utils/signals.py +43 -0
- pythinker_code/utils/slashcmd.py +124 -0
- pythinker_code/utils/string.py +41 -0
- pythinker_code/utils/subprocess_env.py +73 -0
- pythinker_code/utils/term.py +168 -0
- pythinker_code/utils/typing.py +20 -0
- pythinker_code/vis/__init__.py +0 -0
- pythinker_code/vis/api/__init__.py +5 -0
- pythinker_code/vis/api/sessions.py +687 -0
- pythinker_code/vis/api/statistics.py +209 -0
- pythinker_code/vis/api/system.py +19 -0
- pythinker_code/vis/app.py +175 -0
- pythinker_code/vis/static/assets/highlighted-body-B3W2YXNL-D2MTYyJz.js +1 -0
- pythinker_code/vis/static/assets/index-CezafTt_.js +185 -0
- pythinker_code/vis/static/assets/index-DSRInNnm.css +1 -0
- pythinker_code/vis/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- pythinker_code/vis/static/index.html +17 -0
- pythinker_code/web/__init__.py +5 -0
- pythinker_code/web/api/__init__.py +15 -0
- pythinker_code/web/api/config.py +208 -0
- pythinker_code/web/api/open_in.py +199 -0
- pythinker_code/web/api/sessions.py +1225 -0
- pythinker_code/web/app.py +451 -0
- pythinker_code/web/auth.py +191 -0
- pythinker_code/web/models.py +98 -0
- pythinker_code/web/runner/__init__.py +5 -0
- pythinker_code/web/runner/messages.py +57 -0
- pythinker_code/web/runner/process.py +754 -0
- pythinker_code/web/runner/worker.py +97 -0
- pythinker_code/web/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- pythinker_code/web/static/assets/_baseUniq--dyU3g5v.js +1 -0
- pythinker_code/web/static/assets/abap-BdImnpbu.js +1 -0
- pythinker_code/web/static/assets/actionscript-3-CfeIJUat.js +1 -0
- pythinker_code/web/static/assets/ada-bCR0ucgS.js +1 -0
- pythinker_code/web/static/assets/andromeeda-C-Jbm3Hp.js +1 -0
- pythinker_code/web/static/assets/angular-html-CU67Zn6k.js +1 -0
- pythinker_code/web/static/assets/angular-ts-BwZT4LLn.js +1 -0
- pythinker_code/web/static/assets/apache-Pmp26Uib.js +1 -0
- pythinker_code/web/static/assets/apex-D8_7TLub.js +1 -0
- pythinker_code/web/static/assets/apl-dKokRX4l.js +1 -0
- pythinker_code/web/static/assets/applescript-Co6uUVPk.js +1 -0
- pythinker_code/web/static/assets/ara-BRHolxvo.js +1 -0
- pythinker_code/web/static/assets/arc-DkMjLpYa.js +1 -0
- pythinker_code/web/static/assets/architectureDiagram-VXUJARFQ-CHWVaGo9.js +36 -0
- pythinker_code/web/static/assets/asciidoc-Dv7Oe6Be.js +1 -0
- pythinker_code/web/static/assets/asm-D_Q5rh1f.js +1 -0
- pythinker_code/web/static/assets/astro-CbQHKStN.js +1 -0
- pythinker_code/web/static/assets/aurora-x-D-2ljcwZ.js +1 -0
- pythinker_code/web/static/assets/awk-DMzUqQB5.js +1 -0
- pythinker_code/web/static/assets/ayu-dark-CmMr59Fi.js +1 -0
- pythinker_code/web/static/assets/ballerina-BFfxhgS-.js +1 -0
- pythinker_code/web/static/assets/bat-BkioyH1T.js +1 -0
- pythinker_code/web/static/assets/beancount-k_qm7-4y.js +1 -0
- pythinker_code/web/static/assets/berry-uYugtg8r.js +1 -0
- pythinker_code/web/static/assets/bibtex-CHM0blh-.js +1 -0
- pythinker_code/web/static/assets/bicep-Bmn6On1c.js +1 -0
- pythinker_code/web/static/assets/blade-D4QpJJKB.js +1 -0
- pythinker_code/web/static/assets/blockDiagram-VD42YOAC-DzdKe497.js +122 -0
- pythinker_code/web/static/assets/bsl-BO_Y6i37.js +1 -0
- pythinker_code/web/static/assets/c-BIGW1oBm.js +1 -0
- pythinker_code/web/static/assets/c3-VCDPK7BO.js +1 -0
- pythinker_code/web/static/assets/c4Diagram-YG6GDRKO-D84Blotg.js +10 -0
- pythinker_code/web/static/assets/cadence-Bv_4Rxtq.js +1 -0
- pythinker_code/web/static/assets/cairo-KRGpt6FW.js +1 -0
- pythinker_code/web/static/assets/catppuccin-frappe-DFWUc33u.js +1 -0
- pythinker_code/web/static/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
- pythinker_code/web/static/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
- pythinker_code/web/static/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
- pythinker_code/web/static/assets/channel-CllSjjdl.js +1 -0
- pythinker_code/web/static/assets/chunk-4BX2VUAB-C9w8wleE.js +1 -0
- pythinker_code/web/static/assets/chunk-55IACEB6-YlYJ8HnF.js +1 -0
- pythinker_code/web/static/assets/chunk-B4BG7PRW-Bwtz_AHU.js +165 -0
- pythinker_code/web/static/assets/chunk-DI55MBZ5-BbEHkl8h.js +220 -0
- pythinker_code/web/static/assets/chunk-FMBD7UC4-BKPbvjLC.js +15 -0
- pythinker_code/web/static/assets/chunk-QN33PNHL-D73dQvKf.js +1 -0
- pythinker_code/web/static/assets/chunk-QZHKN3VN-zGiLKes_.js +1 -0
- pythinker_code/web/static/assets/chunk-TZMSLE5B-LHJCi2fy.js +1 -0
- pythinker_code/web/static/assets/clarity-D53aC0YG.js +1 -0
- pythinker_code/web/static/assets/classDiagram-2ON5EDUG-vX27iZwa.js +1 -0
- pythinker_code/web/static/assets/classDiagram-v2-WZHVMYZB-vX27iZwa.js +1 -0
- pythinker_code/web/static/assets/clojure-P80f7IUj.js +1 -0
- pythinker_code/web/static/assets/clone-DYBkaPm2.js +1 -0
- pythinker_code/web/static/assets/cmake-D1j8_8rp.js +1 -0
- pythinker_code/web/static/assets/cobol-nwyudZeR.js +1 -0
- pythinker_code/web/static/assets/code-block-IT6T5CEO-NtKViZGl.js +2 -0
- pythinker_code/web/static/assets/codeowners-Bp6g37R7.js +1 -0
- pythinker_code/web/static/assets/codeql-DsOJ9woJ.js +1 -0
- pythinker_code/web/static/assets/coffee-Ch7k5sss.js +1 -0
- pythinker_code/web/static/assets/common-lisp-Cg-RD9OK.js +1 -0
- pythinker_code/web/static/assets/coq-DkFqJrB1.js +1 -0
- pythinker_code/web/static/assets/cose-bilkent-S5V4N54A-DialjZpd.js +1 -0
- pythinker_code/web/static/assets/cpp-CofmeUqb.js +1 -0
- pythinker_code/web/static/assets/crystal-tKQVLTB8.js +1 -0
- pythinker_code/web/static/assets/csharp-K5feNrxe.js +1 -0
- pythinker_code/web/static/assets/css-DPfMkruS.js +1 -0
- pythinker_code/web/static/assets/csv-fuZLfV_i.js +1 -0
- pythinker_code/web/static/assets/cue-D82EKSYY.js +1 -0
- pythinker_code/web/static/assets/cypher-COkxafJQ.js +1 -0
- pythinker_code/web/static/assets/cytoscape.esm-C_Fzpdck.js +321 -0
- pythinker_code/web/static/assets/d-85-TOEBH.js +1 -0
- pythinker_code/web/static/assets/dagre-6UL2VRFP-DfuvkZZ7.js +4 -0
- pythinker_code/web/static/assets/dark-plus-C3mMm8J8.js +1 -0
- pythinker_code/web/static/assets/dart-CF10PKvl.js +1 -0
- pythinker_code/web/static/assets/dax-CEL-wOlO.js +1 -0
- pythinker_code/web/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- pythinker_code/web/static/assets/desktop-BmXAJ9_W.js +1 -0
- pythinker_code/web/static/assets/diagram-PSM6KHXK-DLGctX3v.js +24 -0
- pythinker_code/web/static/assets/diagram-QEK2KX5R-DnxN6S0F.js +43 -0
- pythinker_code/web/static/assets/diagram-S2PKOQOG-Caq_Set9.js +24 -0
- pythinker_code/web/static/assets/diff-D97Zzqfu.js +1 -0
- pythinker_code/web/static/assets/docker-BcOcwvcX.js +1 -0
- pythinker_code/web/static/assets/dotenv-Da5cRb03.js +1 -0
- pythinker_code/web/static/assets/dracula-BzJJZx-M.js +1 -0
- pythinker_code/web/static/assets/dracula-soft-BXkSAIEj.js +1 -0
- pythinker_code/web/static/assets/dream-maker-BtqSS_iP.js +1 -0
- pythinker_code/web/static/assets/edge-BkV0erSs.js +1 -0
- pythinker_code/web/static/assets/elixir-CDX3lj18.js +1 -0
- pythinker_code/web/static/assets/elm-DbKCFpqz.js +1 -0
- pythinker_code/web/static/assets/emacs-lisp-C9XAeP06.js +1 -0
- pythinker_code/web/static/assets/erDiagram-Q2GNP2WA-BgTfALoK.js +60 -0
- pythinker_code/web/static/assets/erb-BOJIQeun.js +1 -0
- pythinker_code/web/static/assets/erlang-DsQrWhSR.js +1 -0
- pythinker_code/web/static/assets/everforest-dark-BgDCqdQA.js +1 -0
- pythinker_code/web/static/assets/everforest-light-C8M2exoo.js +1 -0
- pythinker_code/web/static/assets/fennel-BYunw83y.js +1 -0
- pythinker_code/web/static/assets/fish-BvzEVeQv.js +1 -0
- pythinker_code/web/static/assets/flowDiagram-NV44I4VS-QjW_fnGi.js +162 -0
- pythinker_code/web/static/assets/fluent-C4IJs8-o.js +1 -0
- pythinker_code/web/static/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
- pythinker_code/web/static/assets/fortran-free-form-BxgE0vQu.js +1 -0
- pythinker_code/web/static/assets/fsharp-CXgrBDvD.js +1 -0
- pythinker_code/web/static/assets/ganttDiagram-JELNMOA3-fqi8JFof.js +267 -0
- pythinker_code/web/static/assets/gdresource-B7Tvp0Sc.js +1 -0
- pythinker_code/web/static/assets/gdscript-DTMYz4Jt.js +1 -0
- pythinker_code/web/static/assets/gdshader-DkwncUOv.js +1 -0
- pythinker_code/web/static/assets/genie-D0YGMca9.js +1 -0
- pythinker_code/web/static/assets/gherkin-DyxjwDmM.js +1 -0
- pythinker_code/web/static/assets/git-commit-F4YmCXRG.js +1 -0
- pythinker_code/web/static/assets/git-rebase-r7XF79zn.js +1 -0
- pythinker_code/web/static/assets/gitGraphDiagram-NY62KEGX-i7o6VQ8x.js +65 -0
- pythinker_code/web/static/assets/github-dark-DHJKELXO.js +1 -0
- pythinker_code/web/static/assets/github-dark-default-Cuk6v7N8.js +1 -0
- pythinker_code/web/static/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- pythinker_code/web/static/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- pythinker_code/web/static/assets/github-light-DAi9KRSo.js +1 -0
- pythinker_code/web/static/assets/github-light-default-D7oLnXFd.js +1 -0
- pythinker_code/web/static/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- pythinker_code/web/static/assets/gleam-BspZqrRM.js +1 -0
- pythinker_code/web/static/assets/glimmer-js-Rg0-pVw9.js +1 -0
- pythinker_code/web/static/assets/glimmer-ts-U6CK756n.js +1 -0
- pythinker_code/web/static/assets/glsl-DplSGwfg.js +1 -0
- pythinker_code/web/static/assets/gn-n2N0HUVH.js +1 -0
- pythinker_code/web/static/assets/gnuplot-DdkO51Og.js +1 -0
- pythinker_code/web/static/assets/go-Dn2_MT6a.js +1 -0
- pythinker_code/web/static/assets/graph-C0vZK2pT.js +1 -0
- pythinker_code/web/static/assets/graphql-ChdNCCLP.js +1 -0
- pythinker_code/web/static/assets/groovy-gcz8RCvz.js +1 -0
- pythinker_code/web/static/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- pythinker_code/web/static/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- pythinker_code/web/static/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- pythinker_code/web/static/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- pythinker_code/web/static/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- pythinker_code/web/static/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- pythinker_code/web/static/assets/hack-CaT9iCJl.js +1 -0
- pythinker_code/web/static/assets/haml-B8DHNrY2.js +1 -0
- pythinker_code/web/static/assets/handlebars-BL8al0AC.js +1 -0
- pythinker_code/web/static/assets/haskell-Df6bDoY_.js +1 -0
- pythinker_code/web/static/assets/haxe-CzTSHFRz.js +1 -0
- pythinker_code/web/static/assets/hcl-BWvSN4gD.js +1 -0
- pythinker_code/web/static/assets/hjson-D5-asLiD.js +1 -0
- pythinker_code/web/static/assets/hlsl-D3lLCCz7.js +1 -0
- pythinker_code/web/static/assets/houston-DnULxvSX.js +1 -0
- pythinker_code/web/static/assets/html-GMplVEZG.js +1 -0
- pythinker_code/web/static/assets/html-derivative-BFtXZ54Q.js +1 -0
- pythinker_code/web/static/assets/http-jrhK8wxY.js +1 -0
- pythinker_code/web/static/assets/hurl-irOxFIW8.js +1 -0
- pythinker_code/web/static/assets/hxml-Bvhsp5Yf.js +1 -0
- pythinker_code/web/static/assets/hy-DFXneXwc.js +1 -0
- pythinker_code/web/static/assets/imba-DGztddWO.js +1 -0
- pythinker_code/web/static/assets/index-BYCCk6-K.js +153 -0
- pythinker_code/web/static/assets/index-BpoLgcEt.js +1 -0
- pythinker_code/web/static/assets/index-Cpy4G3uJ.js +2 -0
- pythinker_code/web/static/assets/index-CzV_vCfu.css +1 -0
- pythinker_code/web/static/assets/index-DI2oedCt.js +19 -0
- pythinker_code/web/static/assets/index-DdIkp80K.js +5 -0
- pythinker_code/web/static/assets/infoDiagram-WHAUD3N6-BMPpeZSM.js +2 -0
- pythinker_code/web/static/assets/ini-BEwlwnbL.js +1 -0
- pythinker_code/web/static/assets/init-Gi6I4Gst.js +1 -0
- pythinker_code/web/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- pythinker_code/web/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- pythinker_code/web/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- pythinker_code/web/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- pythinker_code/web/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- pythinker_code/web/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- pythinker_code/web/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- pythinker_code/web/static/assets/java-CylS5w8V.js +1 -0
- pythinker_code/web/static/assets/javascript-wDzz0qaB.js +1 -0
- pythinker_code/web/static/assets/jinja-4LBKfQ-Z.js +1 -0
- pythinker_code/web/static/assets/jison-wvAkD_A8.js +1 -0
- pythinker_code/web/static/assets/journeyDiagram-XKPGCS4Q-DAM7gngo.js +139 -0
- pythinker_code/web/static/assets/json-Cp-IABpG.js +1 -0
- pythinker_code/web/static/assets/json5-C9tS-k6U.js +1 -0
- pythinker_code/web/static/assets/jsonc-Des-eS-w.js +1 -0
- pythinker_code/web/static/assets/jsonl-DcaNXYhu.js +1 -0
- pythinker_code/web/static/assets/jsonnet-DFQXde-d.js +1 -0
- pythinker_code/web/static/assets/jssm-C2t-YnRu.js +1 -0
- pythinker_code/web/static/assets/jsx-g9-lgVsj.js +1 -0
- pythinker_code/web/static/assets/julia-CxzCAyBv.js +1 -0
- pythinker_code/web/static/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- pythinker_code/web/static/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- pythinker_code/web/static/assets/kanagawa-wave-DWedfzmr.js +1 -0
- pythinker_code/web/static/assets/kanban-definition-3W4ZIXB7-ChpBpV0k.js +89 -0
- pythinker_code/web/static/assets/katex-D2lIc1rk.css +1 -0
- pythinker_code/web/static/assets/kdl-DV7GczEv.js +1 -0
- pythinker_code/web/static/assets/kotlin-BdnUsdx6.js +1 -0
- pythinker_code/web/static/assets/kusto-DZf3V79B.js +1 -0
- pythinker_code/web/static/assets/laserwave-DUszq2jm.js +1 -0
- pythinker_code/web/static/assets/latex-B4uzh10-.js +1 -0
- pythinker_code/web/static/assets/layout-C3Jp1gKO.js +1 -0
- pythinker_code/web/static/assets/lean-BZvkOJ9d.js +1 -0
- pythinker_code/web/static/assets/less-B1dDrJ26.js +1 -0
- pythinker_code/web/static/assets/light-plus-B7mTdjB0.js +1 -0
- pythinker_code/web/static/assets/linear-BGHtL1N4.js +1 -0
- pythinker_code/web/static/assets/liquid-DYVedYrR.js +1 -0
- pythinker_code/web/static/assets/llvm-BtvRca6l.js +1 -0
- pythinker_code/web/static/assets/log-2UxHyX5q.js +1 -0
- pythinker_code/web/static/assets/logo-BtOb2qkB.js +1 -0
- pythinker_code/web/static/assets/lua-BbnMAYS6.js +1 -0
- pythinker_code/web/static/assets/luau-C-HG3fhB.js +1 -0
- pythinker_code/web/static/assets/make-CHLpvVh8.js +1 -0
- pythinker_code/web/static/assets/markdown-Cvjx9yec.js +1 -0
- pythinker_code/web/static/assets/marko-DZsq8hO1.js +1 -0
- pythinker_code/web/static/assets/material-theme-D5KoaKCx.js +1 -0
- pythinker_code/web/static/assets/material-theme-darker-BfHTSMKl.js +1 -0
- pythinker_code/web/static/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- pythinker_code/web/static/assets/material-theme-ocean-CyktbL80.js +1 -0
- pythinker_code/web/static/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- pythinker_code/web/static/assets/matlab-D7o27uSR.js +1 -0
- pythinker_code/web/static/assets/mdc-DUICxH0z.js +1 -0
- pythinker_code/web/static/assets/mdx-Cmh6b_Ma.js +1 -0
- pythinker_code/web/static/assets/mermaid-VLURNSYL-B2P5VJ9v.css +1 -0
- pythinker_code/web/static/assets/mermaid-VLURNSYL-C_HW6koB.js +465 -0
- pythinker_code/web/static/assets/mermaid-mWjccvbQ.js +1 -0
- pythinker_code/web/static/assets/mermaid.core-CnT4VrPC.js +191 -0
- pythinker_code/web/static/assets/min-Dn5VRVmX.js +1 -0
- pythinker_code/web/static/assets/min-dark-CafNBF8u.js +1 -0
- pythinker_code/web/static/assets/min-light-CTRr51gU.js +1 -0
- pythinker_code/web/static/assets/mindmap-definition-VGOIOE7T-x8EwhfIt.js +68 -0
- pythinker_code/web/static/assets/mipsasm-CKIfxQSi.js +1 -0
- pythinker_code/web/static/assets/mojo-B93PlW-d.js +1 -0
- pythinker_code/web/static/assets/monokai-D4h5O-jR.js +1 -0
- pythinker_code/web/static/assets/moonbit-Ba13S78F.js +1 -0
- pythinker_code/web/static/assets/move-Bu9oaDYs.js +1 -0
- pythinker_code/web/static/assets/narrat-DRg8JJMk.js +1 -0
- pythinker_code/web/static/assets/nextflow-BrzmwbiE.js +1 -0
- pythinker_code/web/static/assets/nginx-DknmC5AR.js +1 -0
- pythinker_code/web/static/assets/night-owl-C39BiMTA.js +1 -0
- pythinker_code/web/static/assets/nim-CVrawwO9.js +1 -0
- pythinker_code/web/static/assets/nix-CwoSXNpI.js +1 -0
- pythinker_code/web/static/assets/nord-Ddv68eIx.js +1 -0
- pythinker_code/web/static/assets/nushell-C-sUppwS.js +1 -0
- pythinker_code/web/static/assets/objective-c-DXmwc3jG.js +1 -0
- pythinker_code/web/static/assets/objective-cpp-CLxacb5B.js +1 -0
- pythinker_code/web/static/assets/ocaml-C0hk2d4L.js +1 -0
- pythinker_code/web/static/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- pythinker_code/web/static/assets/one-light-PoHY5YXO.js +1 -0
- pythinker_code/web/static/assets/openscad-C4EeE6gA.js +1 -0
- pythinker_code/web/static/assets/ordinal-Cboi1Yqb.js +1 -0
- pythinker_code/web/static/assets/pascal-D93ZcfNL.js +1 -0
- pythinker_code/web/static/assets/perl-C0TMdlhV.js +1 -0
- pythinker_code/web/static/assets/php-CDn_0X-4.js +1 -0
- pythinker_code/web/static/assets/pieDiagram-ADFJNKIX-DgxBKGz2.js +30 -0
- pythinker_code/web/static/assets/pkl-u5AG7uiY.js +1 -0
- pythinker_code/web/static/assets/plastic-3e1v2bzS.js +1 -0
- pythinker_code/web/static/assets/plsql-ChMvpjG-.js +1 -0
- pythinker_code/web/static/assets/po-BTJTHyun.js +1 -0
- pythinker_code/web/static/assets/poimandres-CS3Unz2-.js +1 -0
- pythinker_code/web/static/assets/polar-C0HS_06l.js +1 -0
- pythinker_code/web/static/assets/postcss-CXtECtnM.js +1 -0
- pythinker_code/web/static/assets/powerquery-CEu0bR-o.js +1 -0
- pythinker_code/web/static/assets/powershell-Dpen1YoG.js +1 -0
- pythinker_code/web/static/assets/prisma-Dd19v3D-.js +1 -0
- pythinker_code/web/static/assets/prolog-CbFg5uaA.js +1 -0
- pythinker_code/web/static/assets/proto-C7zT0LnQ.js +1 -0
- pythinker_code/web/static/assets/pug-CGlum2m_.js +1 -0
- pythinker_code/web/static/assets/puppet-BMWR74SV.js +1 -0
- pythinker_code/web/static/assets/purescript-CklMAg4u.js +1 -0
- pythinker_code/web/static/assets/python-B6aJPvgy.js +1 -0
- pythinker_code/web/static/assets/qml-3beO22l8.js +1 -0
- pythinker_code/web/static/assets/qmldir-C8lEn-DE.js +1 -0
- pythinker_code/web/static/assets/qss-IeuSbFQv.js +1 -0
- pythinker_code/web/static/assets/quadrantDiagram-AYHSOK5B-DSpe_dqk.js +7 -0
- pythinker_code/web/static/assets/r-Dspwwk_N.js +1 -0
- pythinker_code/web/static/assets/racket-BqYA7rlc.js +1 -0
- pythinker_code/web/static/assets/raku-DXvB9xmW.js +1 -0
- pythinker_code/web/static/assets/razor-C1TweQQi.js +1 -0
- pythinker_code/web/static/assets/red-bN70gL4F.js +1 -0
- pythinker_code/web/static/assets/reg-C-SQnVFl.js +1 -0
- pythinker_code/web/static/assets/regexp-CDVJQ6XC.js +1 -0
- pythinker_code/web/static/assets/rel-C3B-1QV4.js +1 -0
- pythinker_code/web/static/assets/requirementDiagram-UZGBJVZJ-8o9hozL-.js +64 -0
- pythinker_code/web/static/assets/riscv-BM1_JUlF.js +1 -0
- pythinker_code/web/static/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
- pythinker_code/web/static/assets/rose-pine-moon-D4_iv3hh.js +1 -0
- pythinker_code/web/static/assets/rose-pine-qdsjHGoJ.js +1 -0
- pythinker_code/web/static/assets/rosmsg-BJDFO7_C.js +1 -0
- pythinker_code/web/static/assets/rst-B0xPkSld.js +1 -0
- pythinker_code/web/static/assets/ruby-BvKwtOVI.js +1 -0
- pythinker_code/web/static/assets/rust-B1yitclQ.js +1 -0
- pythinker_code/web/static/assets/sankeyDiagram-TZEHDZUN-BLOSUixH.js +10 -0
- pythinker_code/web/static/assets/sas-cz2c8ADy.js +1 -0
- pythinker_code/web/static/assets/sass-Cj5Yp3dK.js +1 -0
- pythinker_code/web/static/assets/scala-C151Ov-r.js +1 -0
- pythinker_code/web/static/assets/scheme-C98Dy4si.js +1 -0
- pythinker_code/web/static/assets/scss-OYdSNvt2.js +1 -0
- pythinker_code/web/static/assets/sdbl-DVxCFoDh.js +1 -0
- pythinker_code/web/static/assets/sequenceDiagram-WL72ISMW-6F2G8JTU.js +145 -0
- pythinker_code/web/static/assets/shaderlab-Dg9Lc6iA.js +1 -0
- pythinker_code/web/static/assets/shellscript-Yzrsuije.js +1 -0
- pythinker_code/web/static/assets/shellsession-BADoaaVG.js +1 -0
- pythinker_code/web/static/assets/slack-dark-BthQWCQV.js +1 -0
- pythinker_code/web/static/assets/slack-ochin-DqwNpetd.js +1 -0
- pythinker_code/web/static/assets/smalltalk-BERRCDM3.js +1 -0
- pythinker_code/web/static/assets/snazzy-light-Bw305WKR.js +1 -0
- pythinker_code/web/static/assets/solarized-dark-DXbdFlpD.js +1 -0
- pythinker_code/web/static/assets/solarized-light-L9t79GZl.js +1 -0
- pythinker_code/web/static/assets/solidity-rGO070M0.js +1 -0
- pythinker_code/web/static/assets/soy-Brmx7dQM.js +1 -0
- pythinker_code/web/static/assets/sparql-rVzFXLq3.js +1 -0
- pythinker_code/web/static/assets/splunk-BtCnVYZw.js +1 -0
- pythinker_code/web/static/assets/sql-BLtJtn59.js +1 -0
- pythinker_code/web/static/assets/ssh-config-_ykCGR6B.js +1 -0
- pythinker_code/web/static/assets/stata-BH5u7GGu.js +1 -0
- pythinker_code/web/static/assets/stateDiagram-FKZM4ZOC-DP8xP0iJ.js +1 -0
- pythinker_code/web/static/assets/stateDiagram-v2-4FDKWEC3-1l6-EZNX.js +1 -0
- pythinker_code/web/static/assets/stylus-BEDo0Tqx.js +1 -0
- pythinker_code/web/static/assets/svelte-zxCyuUbr.js +1 -0
- pythinker_code/web/static/assets/swift-Dg5xB15N.js +1 -0
- pythinker_code/web/static/assets/synthwave-84-CbfX1IO0.js +1 -0
- pythinker_code/web/static/assets/system-verilog-CnnmHF94.js +1 -0
- pythinker_code/web/static/assets/systemd-4A_iFExJ.js +1 -0
- pythinker_code/web/static/assets/talonscript-CkByrt1z.js +1 -0
- pythinker_code/web/static/assets/tasl-QIJgUcNo.js +1 -0
- pythinker_code/web/static/assets/tcl-dwOrl1Do.js +1 -0
- pythinker_code/web/static/assets/templ-W15q3VgB.js +1 -0
- pythinker_code/web/static/assets/terraform-BETggiCN.js +1 -0
- pythinker_code/web/static/assets/tex-CvyZ59Mk.js +1 -0
- pythinker_code/web/static/assets/timeline-definition-IT6M3QCI-DMgruDfK.js +61 -0
- pythinker_code/web/static/assets/tokyo-night-hegEt444.js +1 -0
- pythinker_code/web/static/assets/toml-vGWfd6FD.js +1 -0
- pythinker_code/web/static/assets/treemap-KMMF4GRG-Bo9ZkrAK.js +128 -0
- pythinker_code/web/static/assets/ts-tags-zn1MmPIZ.js +1 -0
- pythinker_code/web/static/assets/tsv-B_m7g4N7.js +1 -0
- pythinker_code/web/static/assets/tsx-COt5Ahok.js +1 -0
- pythinker_code/web/static/assets/turtle-BsS91CYL.js +1 -0
- pythinker_code/web/static/assets/twig-CO9l9SDP.js +1 -0
- pythinker_code/web/static/assets/typescript-BPQ3VLAy.js +1 -0
- pythinker_code/web/static/assets/typespec-BGHnOYBU.js +1 -0
- pythinker_code/web/static/assets/typst-DHCkPAjA.js +1 -0
- pythinker_code/web/static/assets/v-BcVCzyr7.js +1 -0
- pythinker_code/web/static/assets/vala-CsfeWuGM.js +1 -0
- pythinker_code/web/static/assets/vb-D17OF-Vu.js +1 -0
- pythinker_code/web/static/assets/verilog-BQ8w6xss.js +1 -0
- pythinker_code/web/static/assets/vesper-DU1UobuO.js +1 -0
- pythinker_code/web/static/assets/vhdl-CeAyd5Ju.js +1 -0
- pythinker_code/web/static/assets/viml-CJc9bBzg.js +1 -0
- pythinker_code/web/static/assets/vitesse-black-Bkuqu6BP.js +1 -0
- pythinker_code/web/static/assets/vitesse-dark-D0r3Knsf.js +1 -0
- pythinker_code/web/static/assets/vitesse-light-CVO1_9PV.js +1 -0
- pythinker_code/web/static/assets/vue-DN_0RTcg.js +1 -0
- pythinker_code/web/static/assets/vue-html-AaS7Mt5G.js +1 -0
- pythinker_code/web/static/assets/vue-vine-CQOfvN7w.js +1 -0
- pythinker_code/web/static/assets/vyper-CDx5xZoG.js +1 -0
- pythinker_code/web/static/assets/wasm-CG6Dc4jp.js +1 -0
- pythinker_code/web/static/assets/wasm-MzD3tlZU.js +1 -0
- pythinker_code/web/static/assets/wenyan-BV7otONQ.js +1 -0
- pythinker_code/web/static/assets/wgsl-Dx-B1_4e.js +1 -0
- pythinker_code/web/static/assets/wikitext-BhOHFoWU.js +1 -0
- pythinker_code/web/static/assets/wit-5i3qLPDT.js +1 -0
- pythinker_code/web/static/assets/wolfram-lXgVvXCa.js +1 -0
- pythinker_code/web/static/assets/xml-sdJ4AIDG.js +1 -0
- pythinker_code/web/static/assets/xsl-CtQFsRM5.js +1 -0
- pythinker_code/web/static/assets/xychartDiagram-PRI3JC2R-GeF2johi.js +7 -0
- pythinker_code/web/static/assets/yaml-Buea-lGh.js +1 -0
- pythinker_code/web/static/assets/zenscript-DVFEvuxE.js +1 -0
- pythinker_code/web/static/assets/zig-VOosw3JB.js +1 -0
- pythinker_code/web/static/brand/apple-touch-icon.png +0 -0
- pythinker_code/web/static/brand/arctecture.webp +0 -0
- pythinker_code/web/static/brand/bimi-logo.svg +46 -0
- pythinker_code/web/static/brand/favicon.ico +0 -0
- pythinker_code/web/static/brand/fonts/dm-sans-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/dm-sans-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/instrument-sans-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/instrument-sans-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/instrument-serif-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/instrument-serif-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/libre-baskerville-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/libre-baskerville-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/roboto-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/roboto-latin.woff2 +0 -0
- pythinker_code/web/static/brand/icon-192.png +0 -0
- pythinker_code/web/static/brand/icon-512.png +0 -0
- pythinker_code/web/static/brand/icon.svg +1 -0
- pythinker_code/web/static/brand/logo.png +0 -0
- pythinker_code/web/static/brand/pythinker_animated.svg +79 -0
- pythinker_code/web/static/brand/robots.txt +4 -0
- pythinker_code/web/static/index.html +15 -0
- pythinker_code/web/static/logo.png +0 -0
- pythinker_code/web/store/__init__.py +1 -0
- pythinker_code/web/store/sessions.py +432 -0
- pythinker_code/wire/__init__.py +148 -0
- pythinker_code/wire/file.py +151 -0
- pythinker_code/wire/jsonrpc.py +263 -0
- pythinker_code/wire/protocol.py +2 -0
- pythinker_code/wire/root_hub.py +27 -0
- pythinker_code/wire/serde.py +26 -0
- pythinker_code/wire/server.py +1069 -0
- pythinker_code/wire/types.py +698 -0
- pythinker_code-2.0.0.dist-info/METADATA +660 -0
- pythinker_code-2.0.0.dist-info/RECORD +731 -0
- pythinker_code-2.0.0.dist-info/WHEEL +4 -0
- pythinker_code-2.0.0.dist-info/entry_points.txt +4 -0
- pythinker_code-2.0.0.dist-info/licenses/LICENSE +202 -0
- 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)
|