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,496 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import uuid
|
|
5
|
+
from contextvars import ContextVar
|
|
6
|
+
|
|
7
|
+
import acp
|
|
8
|
+
import streamingjson # type: ignore[reportMissingTypeStubs]
|
|
9
|
+
from pythinker_core.chat_provider import APIStatusError, ChatProviderError
|
|
10
|
+
from pythinker_host import Host, reset_current_host, set_current_host
|
|
11
|
+
|
|
12
|
+
from pythinker_code.acp.convert import (
|
|
13
|
+
acp_blocks_to_content_parts,
|
|
14
|
+
display_block_to_acp_content,
|
|
15
|
+
tool_result_to_acp_content,
|
|
16
|
+
)
|
|
17
|
+
from pythinker_code.acp.types import ACPContentBlock
|
|
18
|
+
from pythinker_code.app import PythinkerCLI
|
|
19
|
+
from pythinker_code.soul import LLMNotSet, LLMNotSupported, MaxStepsReached, RunCancelled
|
|
20
|
+
from pythinker_code.tools import extract_key_argument
|
|
21
|
+
from pythinker_code.utils.logging import logger
|
|
22
|
+
from pythinker_code.wire.types import (
|
|
23
|
+
ApprovalRequest,
|
|
24
|
+
ApprovalResponse,
|
|
25
|
+
CompactionBegin,
|
|
26
|
+
CompactionEnd,
|
|
27
|
+
ContentPart,
|
|
28
|
+
MCPLoadingBegin,
|
|
29
|
+
MCPLoadingEnd,
|
|
30
|
+
Notification,
|
|
31
|
+
PlanDisplay,
|
|
32
|
+
QuestionRequest,
|
|
33
|
+
StatusUpdate,
|
|
34
|
+
SteerInput,
|
|
35
|
+
StepBegin,
|
|
36
|
+
StepInterrupted,
|
|
37
|
+
SubagentEvent,
|
|
38
|
+
TextPart,
|
|
39
|
+
ThinkPart,
|
|
40
|
+
TodoDisplayBlock,
|
|
41
|
+
ToolCall,
|
|
42
|
+
ToolCallPart,
|
|
43
|
+
ToolCallRequest,
|
|
44
|
+
ToolResult,
|
|
45
|
+
TurnBegin,
|
|
46
|
+
TurnEnd,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
_current_turn_id = ContextVar[str | None]("current_turn_id", default=None)
|
|
50
|
+
_terminal_tool_call_ids = ContextVar[set[str] | None]("terminal_tool_call_ids", default=None)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_current_acp_tool_call_id_or_none() -> str | None:
|
|
54
|
+
"""See `_ToolCallState.acp_tool_call_id`."""
|
|
55
|
+
from pythinker_code.soul.toolset import get_current_tool_call_or_none
|
|
56
|
+
|
|
57
|
+
turn_id = _current_turn_id.get()
|
|
58
|
+
if turn_id is None:
|
|
59
|
+
return None
|
|
60
|
+
tool_call = get_current_tool_call_or_none()
|
|
61
|
+
if tool_call is None:
|
|
62
|
+
return None
|
|
63
|
+
return f"{turn_id}/{tool_call.id}"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def register_terminal_tool_call_id(tool_call_id: str) -> None:
|
|
67
|
+
calls = _terminal_tool_call_ids.get()
|
|
68
|
+
if calls is not None:
|
|
69
|
+
calls.add(tool_call_id)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def should_hide_terminal_output(tool_call_id: str) -> bool:
|
|
73
|
+
calls = _terminal_tool_call_ids.get()
|
|
74
|
+
return calls is not None and tool_call_id in calls
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class _ToolCallState:
|
|
78
|
+
"""Manages the state of a single tool call for streaming updates."""
|
|
79
|
+
|
|
80
|
+
def __init__(self, tool_call: ToolCall):
|
|
81
|
+
self.tool_call = tool_call
|
|
82
|
+
self.args = tool_call.function.arguments or ""
|
|
83
|
+
self.lexer = streamingjson.Lexer()
|
|
84
|
+
if tool_call.function.arguments is not None:
|
|
85
|
+
self.lexer.append_string(tool_call.function.arguments)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def acp_tool_call_id(self) -> str:
|
|
89
|
+
# When the user rejected or cancelled a tool call, the step result may not
|
|
90
|
+
# be appended to the context. In this case, future step may emit tool call
|
|
91
|
+
# with the same tool call ID (on the LLM side). To avoid confusion of the
|
|
92
|
+
# ACP client, we ensure the uniqueness by prefixing with the turn ID.
|
|
93
|
+
turn_id = _current_turn_id.get()
|
|
94
|
+
assert turn_id is not None
|
|
95
|
+
return f"{turn_id}/{self.tool_call.id}"
|
|
96
|
+
|
|
97
|
+
def append_args_part(self, args_part: str) -> None:
|
|
98
|
+
"""Append a new arguments part to the accumulated args and lexer."""
|
|
99
|
+
self.args += args_part
|
|
100
|
+
self.lexer.append_string(args_part)
|
|
101
|
+
|
|
102
|
+
def get_title(self) -> str:
|
|
103
|
+
"""Get the current title with subtitle if available."""
|
|
104
|
+
tool_name = self.tool_call.function.name
|
|
105
|
+
subtitle = extract_key_argument(self.lexer, tool_name)
|
|
106
|
+
if subtitle:
|
|
107
|
+
return f"{tool_name}: {subtitle}"
|
|
108
|
+
return tool_name
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class _TurnState:
|
|
112
|
+
def __init__(self):
|
|
113
|
+
self.id = str(uuid.uuid4())
|
|
114
|
+
"""Unique ID for the turn."""
|
|
115
|
+
self.tool_calls: dict[str, _ToolCallState] = {}
|
|
116
|
+
"""Map of tool call ID (LLM-side ID) to tool call state."""
|
|
117
|
+
self.last_tool_call: _ToolCallState | None = None
|
|
118
|
+
self.cancel_event = asyncio.Event()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class ACPSession:
|
|
122
|
+
def __init__(
|
|
123
|
+
self,
|
|
124
|
+
id: str,
|
|
125
|
+
cli: PythinkerCLI,
|
|
126
|
+
acp_conn: acp.Client,
|
|
127
|
+
host: Host | None = None,
|
|
128
|
+
) -> None:
|
|
129
|
+
self._id = id
|
|
130
|
+
self._cli = cli
|
|
131
|
+
self._conn = acp_conn
|
|
132
|
+
self._host = host
|
|
133
|
+
self._turn_state: _TurnState | None = None
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def id(self) -> str:
|
|
137
|
+
"""The ID of the ACP session."""
|
|
138
|
+
return self._id
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def cli(self) -> PythinkerCLI:
|
|
142
|
+
"""The Pythinker CLI instance bound to this ACP session."""
|
|
143
|
+
return self._cli
|
|
144
|
+
|
|
145
|
+
def _is_oauth_session(self) -> bool:
|
|
146
|
+
"""Return True if the current session uses OAuth-based authentication."""
|
|
147
|
+
try:
|
|
148
|
+
llm = self._cli.soul.runtime.llm
|
|
149
|
+
return llm is not None and getattr(llm.provider_config, "oauth", None) is not None
|
|
150
|
+
except AttributeError:
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
async def prompt(self, prompt: list[ACPContentBlock]) -> acp.PromptResponse:
|
|
154
|
+
user_input = acp_blocks_to_content_parts(prompt)
|
|
155
|
+
self._turn_state = _TurnState()
|
|
156
|
+
token = _current_turn_id.set(self._turn_state.id)
|
|
157
|
+
host_token = set_current_host(self._host) if self._host is not None else None
|
|
158
|
+
terminal_tool_calls_token = _terminal_tool_call_ids.set(set())
|
|
159
|
+
try:
|
|
160
|
+
async for msg in self._cli.run(user_input, self._turn_state.cancel_event):
|
|
161
|
+
match msg:
|
|
162
|
+
case TurnBegin():
|
|
163
|
+
pass
|
|
164
|
+
case SteerInput():
|
|
165
|
+
pass
|
|
166
|
+
case TurnEnd():
|
|
167
|
+
pass
|
|
168
|
+
case StepBegin():
|
|
169
|
+
pass
|
|
170
|
+
case StepInterrupted():
|
|
171
|
+
break
|
|
172
|
+
case CompactionBegin():
|
|
173
|
+
pass
|
|
174
|
+
case CompactionEnd():
|
|
175
|
+
pass
|
|
176
|
+
case MCPLoadingBegin():
|
|
177
|
+
pass
|
|
178
|
+
case MCPLoadingEnd():
|
|
179
|
+
pass
|
|
180
|
+
case StatusUpdate():
|
|
181
|
+
pass
|
|
182
|
+
case Notification():
|
|
183
|
+
await self._send_notification(msg)
|
|
184
|
+
case ThinkPart(think=think):
|
|
185
|
+
await self._send_thinking(think)
|
|
186
|
+
case TextPart(text=text):
|
|
187
|
+
await self._send_text(text)
|
|
188
|
+
case ContentPart():
|
|
189
|
+
logger.warning("Unsupported content part: {part}", part=msg)
|
|
190
|
+
await self._send_text(f"[{msg.__class__.__name__}]")
|
|
191
|
+
case ToolCall():
|
|
192
|
+
await self._send_tool_call(msg)
|
|
193
|
+
case ToolCallPart():
|
|
194
|
+
await self._send_tool_call_part(msg)
|
|
195
|
+
case ToolResult():
|
|
196
|
+
await self._send_tool_result(msg)
|
|
197
|
+
case ApprovalResponse():
|
|
198
|
+
pass
|
|
199
|
+
case SubagentEvent():
|
|
200
|
+
pass
|
|
201
|
+
case PlanDisplay():
|
|
202
|
+
pass
|
|
203
|
+
case ApprovalRequest():
|
|
204
|
+
await self._handle_approval_request(msg)
|
|
205
|
+
case ToolCallRequest():
|
|
206
|
+
logger.warning("Unexpected ToolCallRequest in ACP session: {msg}", msg=msg)
|
|
207
|
+
case QuestionRequest():
|
|
208
|
+
logger.warning(
|
|
209
|
+
"QuestionRequest is unsupported in ACP session; resolving empty answer."
|
|
210
|
+
)
|
|
211
|
+
msg.resolve({})
|
|
212
|
+
case _:
|
|
213
|
+
pass
|
|
214
|
+
except LLMNotSet as e:
|
|
215
|
+
logger.exception("LLM not set:")
|
|
216
|
+
raise acp.RequestError.auth_required() from e
|
|
217
|
+
except LLMNotSupported as e:
|
|
218
|
+
logger.exception("LLM not supported:")
|
|
219
|
+
raise acp.RequestError.internal_error({"error": str(e)}) from e
|
|
220
|
+
except APIStatusError as e:
|
|
221
|
+
if e.status_code == 401 and self._is_oauth_session():
|
|
222
|
+
logger.warning("Authentication failed (401), prompting re-login")
|
|
223
|
+
raise acp.RequestError.auth_required() from e
|
|
224
|
+
logger.exception("LLM API status error:")
|
|
225
|
+
raise acp.RequestError.internal_error({"error": str(e)}) from e
|
|
226
|
+
except ChatProviderError as e:
|
|
227
|
+
logger.exception("LLM provider error:")
|
|
228
|
+
raise acp.RequestError.internal_error({"error": str(e)}) from e
|
|
229
|
+
except MaxStepsReached as e:
|
|
230
|
+
logger.warning("Max steps reached: {n_steps}", n_steps=e.n_steps)
|
|
231
|
+
return acp.PromptResponse(stop_reason="max_turn_requests")
|
|
232
|
+
except RunCancelled:
|
|
233
|
+
logger.info("Prompt cancelled by user")
|
|
234
|
+
return acp.PromptResponse(stop_reason="cancelled")
|
|
235
|
+
except Exception as e:
|
|
236
|
+
logger.exception("Unexpected error during prompt:")
|
|
237
|
+
raise acp.RequestError.internal_error({"error": str(e)}) from e
|
|
238
|
+
finally:
|
|
239
|
+
self._turn_state = None
|
|
240
|
+
if host_token is not None:
|
|
241
|
+
reset_current_host(host_token)
|
|
242
|
+
_terminal_tool_call_ids.reset(terminal_tool_calls_token)
|
|
243
|
+
_current_turn_id.reset(token)
|
|
244
|
+
return acp.PromptResponse(stop_reason="end_turn")
|
|
245
|
+
|
|
246
|
+
async def cancel(self) -> None:
|
|
247
|
+
if self._turn_state is None:
|
|
248
|
+
logger.warning("Cancel requested but no prompt is running")
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
self._turn_state.cancel_event.set()
|
|
252
|
+
|
|
253
|
+
async def _send_thinking(self, think: str):
|
|
254
|
+
"""Send thinking content to client."""
|
|
255
|
+
if not self._id or not self._conn:
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
await self._conn.session_update(
|
|
259
|
+
self._id,
|
|
260
|
+
acp.schema.AgentThoughtChunk(
|
|
261
|
+
content=acp.schema.TextContentBlock(type="text", text=think),
|
|
262
|
+
session_update="agent_thought_chunk",
|
|
263
|
+
),
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
async def _send_text(self, text: str):
|
|
267
|
+
"""Send text chunk to client."""
|
|
268
|
+
if not self._id or not self._conn:
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
await self._conn.session_update(
|
|
272
|
+
session_id=self._id,
|
|
273
|
+
update=acp.schema.AgentMessageChunk(
|
|
274
|
+
content=acp.schema.TextContentBlock(type="text", text=text),
|
|
275
|
+
session_update="agent_message_chunk",
|
|
276
|
+
),
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
async def _send_notification(self, notification: Notification):
|
|
280
|
+
"""Send a system notification to the client as a text chunk."""
|
|
281
|
+
body = notification.body.strip()
|
|
282
|
+
text = f"[Notification] {notification.title}"
|
|
283
|
+
if body:
|
|
284
|
+
text = f"{text}\n{body}"
|
|
285
|
+
await self._send_text(text)
|
|
286
|
+
|
|
287
|
+
async def _send_tool_call(self, tool_call: ToolCall):
|
|
288
|
+
"""Send tool call to client."""
|
|
289
|
+
assert self._turn_state is not None
|
|
290
|
+
if not self._id or not self._conn:
|
|
291
|
+
return
|
|
292
|
+
|
|
293
|
+
# Create and store tool call state
|
|
294
|
+
state = _ToolCallState(tool_call)
|
|
295
|
+
self._turn_state.tool_calls[tool_call.id] = state
|
|
296
|
+
self._turn_state.last_tool_call = state
|
|
297
|
+
|
|
298
|
+
await self._conn.session_update(
|
|
299
|
+
session_id=self._id,
|
|
300
|
+
update=acp.schema.ToolCallStart(
|
|
301
|
+
session_update="tool_call",
|
|
302
|
+
tool_call_id=state.acp_tool_call_id,
|
|
303
|
+
title=state.get_title(),
|
|
304
|
+
status="in_progress",
|
|
305
|
+
content=[
|
|
306
|
+
acp.schema.ContentToolCallContent(
|
|
307
|
+
type="content",
|
|
308
|
+
content=acp.schema.TextContentBlock(type="text", text=state.args),
|
|
309
|
+
)
|
|
310
|
+
],
|
|
311
|
+
),
|
|
312
|
+
)
|
|
313
|
+
logger.debug("Sent tool call: {name}", name=tool_call.function.name)
|
|
314
|
+
|
|
315
|
+
async def _send_tool_call_part(self, part: ToolCallPart):
|
|
316
|
+
"""Send tool call part (streaming arguments)."""
|
|
317
|
+
assert self._turn_state is not None
|
|
318
|
+
if (
|
|
319
|
+
not self._id
|
|
320
|
+
or not self._conn
|
|
321
|
+
or not part.arguments_part
|
|
322
|
+
or self._turn_state.last_tool_call is None
|
|
323
|
+
):
|
|
324
|
+
return
|
|
325
|
+
|
|
326
|
+
# Append new arguments part to the last tool call
|
|
327
|
+
self._turn_state.last_tool_call.append_args_part(part.arguments_part)
|
|
328
|
+
|
|
329
|
+
# Update the tool call with new content and title
|
|
330
|
+
update = acp.schema.ToolCallProgress(
|
|
331
|
+
session_update="tool_call_update",
|
|
332
|
+
tool_call_id=self._turn_state.last_tool_call.acp_tool_call_id,
|
|
333
|
+
title=self._turn_state.last_tool_call.get_title(),
|
|
334
|
+
status="in_progress",
|
|
335
|
+
content=[
|
|
336
|
+
acp.schema.ContentToolCallContent(
|
|
337
|
+
type="content",
|
|
338
|
+
content=acp.schema.TextContentBlock(
|
|
339
|
+
type="text", text=self._turn_state.last_tool_call.args
|
|
340
|
+
),
|
|
341
|
+
)
|
|
342
|
+
],
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
await self._conn.session_update(session_id=self._id, update=update)
|
|
346
|
+
logger.debug("Sent tool call update: {delta}", delta=part.arguments_part[:50])
|
|
347
|
+
|
|
348
|
+
async def _send_tool_result(self, result: ToolResult):
|
|
349
|
+
"""Send tool result to client."""
|
|
350
|
+
assert self._turn_state is not None
|
|
351
|
+
if not self._id or not self._conn:
|
|
352
|
+
return
|
|
353
|
+
|
|
354
|
+
tool_ret = result.return_value
|
|
355
|
+
|
|
356
|
+
state = self._turn_state.tool_calls.pop(result.tool_call_id, None)
|
|
357
|
+
if state is None:
|
|
358
|
+
logger.warning("Tool call not found: {id}", id=result.tool_call_id)
|
|
359
|
+
return
|
|
360
|
+
|
|
361
|
+
update = acp.schema.ToolCallProgress(
|
|
362
|
+
session_update="tool_call_update",
|
|
363
|
+
tool_call_id=state.acp_tool_call_id,
|
|
364
|
+
status="failed" if tool_ret.is_error else "completed",
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
contents = (
|
|
368
|
+
[]
|
|
369
|
+
if should_hide_terminal_output(state.acp_tool_call_id)
|
|
370
|
+
else tool_result_to_acp_content(tool_ret)
|
|
371
|
+
)
|
|
372
|
+
if contents:
|
|
373
|
+
update.content = contents
|
|
374
|
+
|
|
375
|
+
await self._conn.session_update(session_id=self._id, update=update)
|
|
376
|
+
logger.debug("Sent tool result: {id}", id=result.tool_call_id)
|
|
377
|
+
|
|
378
|
+
for block in tool_ret.display:
|
|
379
|
+
if isinstance(block, TodoDisplayBlock):
|
|
380
|
+
await self._send_plan_update(block)
|
|
381
|
+
|
|
382
|
+
async def _handle_approval_request(self, request: ApprovalRequest):
|
|
383
|
+
"""Handle approval request by sending permission request to client."""
|
|
384
|
+
assert self._turn_state is not None
|
|
385
|
+
if not self._id or not self._conn:
|
|
386
|
+
logger.warning("No session ID, auto-rejecting approval request")
|
|
387
|
+
request.resolve("reject")
|
|
388
|
+
return
|
|
389
|
+
|
|
390
|
+
state = self._turn_state.tool_calls.get(request.tool_call_id, None)
|
|
391
|
+
if state is None:
|
|
392
|
+
logger.warning("Tool call not found: {id}", id=request.tool_call_id)
|
|
393
|
+
request.resolve("reject")
|
|
394
|
+
return
|
|
395
|
+
|
|
396
|
+
try:
|
|
397
|
+
content: list[
|
|
398
|
+
acp.schema.ContentToolCallContent
|
|
399
|
+
| acp.schema.FileEditToolCallContent
|
|
400
|
+
| acp.schema.TerminalToolCallContent
|
|
401
|
+
] = []
|
|
402
|
+
if request.display:
|
|
403
|
+
for block in request.display:
|
|
404
|
+
diff_content = display_block_to_acp_content(block)
|
|
405
|
+
if diff_content is not None:
|
|
406
|
+
content.append(diff_content)
|
|
407
|
+
if not content:
|
|
408
|
+
content.append(
|
|
409
|
+
acp.schema.ContentToolCallContent(
|
|
410
|
+
type="content",
|
|
411
|
+
content=acp.schema.TextContentBlock(
|
|
412
|
+
type="text",
|
|
413
|
+
text=f"Requesting approval to perform: {request.description}",
|
|
414
|
+
),
|
|
415
|
+
)
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
# Send permission request and wait for response
|
|
419
|
+
logger.debug("Requesting permission for action: {action}", action=request.action)
|
|
420
|
+
response = await self._conn.request_permission(
|
|
421
|
+
[
|
|
422
|
+
acp.schema.PermissionOption(
|
|
423
|
+
option_id="approve",
|
|
424
|
+
name="Approve once",
|
|
425
|
+
kind="allow_once",
|
|
426
|
+
),
|
|
427
|
+
acp.schema.PermissionOption(
|
|
428
|
+
option_id="approve_for_session",
|
|
429
|
+
name="Approve for this session",
|
|
430
|
+
kind="allow_always",
|
|
431
|
+
),
|
|
432
|
+
acp.schema.PermissionOption(
|
|
433
|
+
option_id="reject",
|
|
434
|
+
name="Reject",
|
|
435
|
+
kind="reject_once",
|
|
436
|
+
),
|
|
437
|
+
],
|
|
438
|
+
self._id,
|
|
439
|
+
acp.schema.ToolCallUpdate(
|
|
440
|
+
tool_call_id=state.acp_tool_call_id,
|
|
441
|
+
title=state.get_title(),
|
|
442
|
+
content=content,
|
|
443
|
+
),
|
|
444
|
+
)
|
|
445
|
+
logger.debug("Received permission response: {response}", response=response)
|
|
446
|
+
|
|
447
|
+
# Process the outcome
|
|
448
|
+
if isinstance(response.outcome, acp.schema.AllowedOutcome):
|
|
449
|
+
# selected
|
|
450
|
+
option_id = response.outcome.option_id
|
|
451
|
+
if option_id == "approve":
|
|
452
|
+
logger.debug("Permission granted for: {action}", action=request.action)
|
|
453
|
+
request.resolve("approve")
|
|
454
|
+
elif option_id == "approve_for_session":
|
|
455
|
+
logger.debug("Permission granted for session: {action}", action=request.action)
|
|
456
|
+
request.resolve("approve_for_session")
|
|
457
|
+
else:
|
|
458
|
+
logger.debug("Permission denied for: {action}", action=request.action)
|
|
459
|
+
request.resolve("reject")
|
|
460
|
+
else:
|
|
461
|
+
# cancelled
|
|
462
|
+
logger.debug("Permission request cancelled for: {action}", action=request.action)
|
|
463
|
+
request.resolve("reject")
|
|
464
|
+
except Exception:
|
|
465
|
+
logger.exception("Error handling approval request:")
|
|
466
|
+
# On error, reject the request
|
|
467
|
+
request.resolve("reject")
|
|
468
|
+
|
|
469
|
+
async def _send_plan_update(self, block: TodoDisplayBlock) -> None:
|
|
470
|
+
"""Send todo list updates as ACP agent plan updates."""
|
|
471
|
+
|
|
472
|
+
status_map: dict[str, acp.schema.PlanEntryStatus] = {
|
|
473
|
+
"pending": "pending",
|
|
474
|
+
"in progress": "in_progress",
|
|
475
|
+
"in_progress": "in_progress",
|
|
476
|
+
"done": "completed",
|
|
477
|
+
"completed": "completed",
|
|
478
|
+
}
|
|
479
|
+
entries: list[acp.schema.PlanEntry] = [
|
|
480
|
+
acp.schema.PlanEntry(
|
|
481
|
+
content=todo.title,
|
|
482
|
+
priority="medium",
|
|
483
|
+
status=status_map.get(todo.status.lower(), "pending"),
|
|
484
|
+
)
|
|
485
|
+
for todo in block.items
|
|
486
|
+
if todo.title
|
|
487
|
+
]
|
|
488
|
+
|
|
489
|
+
if not entries:
|
|
490
|
+
logger.warning("No valid todo items to send in plan update: {todos}", todos=block.items)
|
|
491
|
+
return
|
|
492
|
+
|
|
493
|
+
await self._conn.session_update(
|
|
494
|
+
session_id=self._id,
|
|
495
|
+
update=acp.schema.AgentPlanUpdate(session_update="plan", entries=entries),
|
|
496
|
+
)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from contextlib import suppress
|
|
3
|
+
|
|
4
|
+
import acp
|
|
5
|
+
from pythinker_core.tooling import CallableTool2, ToolReturnValue
|
|
6
|
+
from pythinker_host import get_current_host
|
|
7
|
+
from pythinker_host.local import local_host
|
|
8
|
+
|
|
9
|
+
from pythinker_code.soul.agent import Runtime
|
|
10
|
+
from pythinker_code.soul.approval import Approval
|
|
11
|
+
from pythinker_code.soul.toolset import PythinkerToolset
|
|
12
|
+
from pythinker_code.tools.shell import Params as ShellParams
|
|
13
|
+
from pythinker_code.tools.shell import Shell
|
|
14
|
+
from pythinker_code.tools.utils import ToolResultBuilder
|
|
15
|
+
from pythinker_code.wire.types import DisplayBlock
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def replace_tools(
|
|
19
|
+
client_capabilities: acp.schema.ClientCapabilities,
|
|
20
|
+
acp_conn: acp.Client,
|
|
21
|
+
acp_session_id: str,
|
|
22
|
+
toolset: PythinkerToolset,
|
|
23
|
+
runtime: Runtime,
|
|
24
|
+
) -> None:
|
|
25
|
+
current_host = get_current_host().name
|
|
26
|
+
if current_host not in (local_host.name, "acp"):
|
|
27
|
+
# Only replace tools when running locally or under ACPHost.
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
if client_capabilities.terminal and (shell_tool := toolset.find(Shell)):
|
|
31
|
+
# Replace the Shell tool with the ACP Terminal tool if supported.
|
|
32
|
+
toolset.add(
|
|
33
|
+
Terminal(
|
|
34
|
+
shell_tool,
|
|
35
|
+
acp_conn,
|
|
36
|
+
acp_session_id,
|
|
37
|
+
runtime.approval,
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class HideOutputDisplayBlock(DisplayBlock):
|
|
43
|
+
"""A special DisplayBlock that indicates output should be hidden in ACP clients."""
|
|
44
|
+
|
|
45
|
+
type: str = "acp/hide_output"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Terminal(CallableTool2[ShellParams]):
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
shell_tool: Shell,
|
|
52
|
+
acp_conn: acp.Client,
|
|
53
|
+
acp_session_id: str,
|
|
54
|
+
approval: Approval,
|
|
55
|
+
) -> None:
|
|
56
|
+
# Use the `name`, `description`, and `params` from the existing Shell tool,
|
|
57
|
+
# so that when this is added to the toolset, it replaces the original Shell tool.
|
|
58
|
+
super().__init__(shell_tool.name, shell_tool.description, shell_tool.params)
|
|
59
|
+
self._acp_conn = acp_conn
|
|
60
|
+
self._acp_session_id = acp_session_id
|
|
61
|
+
self._approval = approval
|
|
62
|
+
|
|
63
|
+
async def __call__(self, params: ShellParams) -> ToolReturnValue:
|
|
64
|
+
from pythinker_code.acp.session import get_current_acp_tool_call_id_or_none
|
|
65
|
+
|
|
66
|
+
builder = ToolResultBuilder()
|
|
67
|
+
# Hide tool output because we use `TerminalToolCallContent` which already streams output
|
|
68
|
+
# directly to the user.
|
|
69
|
+
builder.display(HideOutputDisplayBlock())
|
|
70
|
+
|
|
71
|
+
if not params.command:
|
|
72
|
+
return builder.error("Command cannot be empty.", brief="Empty command")
|
|
73
|
+
|
|
74
|
+
approval_result = await self._approval.request(
|
|
75
|
+
self.name,
|
|
76
|
+
"run shell command",
|
|
77
|
+
f"Run command `{params.command}`",
|
|
78
|
+
)
|
|
79
|
+
if not approval_result:
|
|
80
|
+
return approval_result.rejection_error()
|
|
81
|
+
|
|
82
|
+
timeout_seconds = float(params.timeout)
|
|
83
|
+
timeout_label = f"{timeout_seconds:g}s"
|
|
84
|
+
terminal_id: str | None = None
|
|
85
|
+
exit_status: (
|
|
86
|
+
acp.schema.WaitForTerminalExitResponse | acp.schema.TerminalExitStatus | None
|
|
87
|
+
) = None
|
|
88
|
+
timed_out = False
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
resp = await self._acp_conn.create_terminal(
|
|
92
|
+
command=params.command,
|
|
93
|
+
session_id=self._acp_session_id,
|
|
94
|
+
output_byte_limit=builder.max_chars,
|
|
95
|
+
)
|
|
96
|
+
terminal_id = resp.terminal_id
|
|
97
|
+
|
|
98
|
+
acp_tool_call_id = get_current_acp_tool_call_id_or_none()
|
|
99
|
+
assert acp_tool_call_id, "Expected to have an ACP tool call ID in context"
|
|
100
|
+
await self._acp_conn.session_update(
|
|
101
|
+
session_id=self._acp_session_id,
|
|
102
|
+
update=acp.schema.ToolCallProgress(
|
|
103
|
+
session_update="tool_call_update",
|
|
104
|
+
tool_call_id=acp_tool_call_id,
|
|
105
|
+
status="in_progress",
|
|
106
|
+
content=[
|
|
107
|
+
acp.schema.TerminalToolCallContent(
|
|
108
|
+
type="terminal",
|
|
109
|
+
terminal_id=terminal_id,
|
|
110
|
+
)
|
|
111
|
+
],
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
async with asyncio.timeout(timeout_seconds):
|
|
117
|
+
exit_status = await self._acp_conn.wait_for_terminal_exit(
|
|
118
|
+
session_id=self._acp_session_id,
|
|
119
|
+
terminal_id=terminal_id,
|
|
120
|
+
)
|
|
121
|
+
except TimeoutError:
|
|
122
|
+
timed_out = True
|
|
123
|
+
await self._acp_conn.kill_terminal(
|
|
124
|
+
session_id=self._acp_session_id,
|
|
125
|
+
terminal_id=terminal_id,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
output_response = await self._acp_conn.terminal_output(
|
|
129
|
+
session_id=self._acp_session_id,
|
|
130
|
+
terminal_id=terminal_id,
|
|
131
|
+
)
|
|
132
|
+
builder.write(output_response.output)
|
|
133
|
+
if output_response.exit_status:
|
|
134
|
+
exit_status = output_response.exit_status
|
|
135
|
+
|
|
136
|
+
exit_code = exit_status.exit_code if exit_status else None
|
|
137
|
+
exit_signal = exit_status.signal if exit_status else None
|
|
138
|
+
|
|
139
|
+
truncated_note = (
|
|
140
|
+
" Output was truncated by the client output limit."
|
|
141
|
+
if output_response.truncated
|
|
142
|
+
else ""
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if timed_out:
|
|
146
|
+
return builder.error(
|
|
147
|
+
f"Command killed by timeout ({timeout_label}){truncated_note}",
|
|
148
|
+
brief=f"Killed by timeout ({timeout_label})",
|
|
149
|
+
)
|
|
150
|
+
if exit_signal:
|
|
151
|
+
return builder.error(
|
|
152
|
+
f"Command terminated by signal: {exit_signal}.{truncated_note}",
|
|
153
|
+
brief=f"Signal: {exit_signal}",
|
|
154
|
+
)
|
|
155
|
+
if exit_code not in (None, 0):
|
|
156
|
+
return builder.error(
|
|
157
|
+
f"Command failed with exit code: {exit_code}.{truncated_note}",
|
|
158
|
+
brief=f"Failed with exit code: {exit_code}",
|
|
159
|
+
)
|
|
160
|
+
return builder.ok(f"Command executed successfully.{truncated_note}")
|
|
161
|
+
finally:
|
|
162
|
+
if terminal_id is not None:
|
|
163
|
+
with suppress(Exception):
|
|
164
|
+
await self._acp_conn.release_terminal(
|
|
165
|
+
session_id=self._acp_session_id,
|
|
166
|
+
terminal_id=terminal_id,
|
|
167
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import acp
|
|
4
|
+
|
|
5
|
+
MCPServer = acp.schema.HttpMcpServer | acp.schema.SseMcpServer | acp.schema.McpServerStdio
|
|
6
|
+
|
|
7
|
+
ACPContentBlock = (
|
|
8
|
+
acp.schema.TextContentBlock
|
|
9
|
+
| acp.schema.ImageContentBlock
|
|
10
|
+
| acp.schema.AudioContentBlock
|
|
11
|
+
| acp.schema.ResourceContentBlock
|
|
12
|
+
| acp.schema.EmbeddedResourceContentBlock
|
|
13
|
+
)
|