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,1069 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import contextlib
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any, Literal, cast
|
|
7
|
+
|
|
8
|
+
import acp # type: ignore[reportMissingTypeStubs]
|
|
9
|
+
import pydantic
|
|
10
|
+
from pythinker_core.chat_provider import APIStatusError, ChatProviderError
|
|
11
|
+
from pythinker_core.tooling import ToolError, ToolResult
|
|
12
|
+
from pythinker_core.utils.typing import JsonType
|
|
13
|
+
|
|
14
|
+
from pythinker_code.approval_runtime import ApprovalRuntime
|
|
15
|
+
from pythinker_code.constant import USER_AGENT
|
|
16
|
+
from pythinker_code.soul import (
|
|
17
|
+
LLMNotSet,
|
|
18
|
+
LLMNotSupported,
|
|
19
|
+
MaxStepsReached,
|
|
20
|
+
RunCancelled,
|
|
21
|
+
Soul,
|
|
22
|
+
run_soul,
|
|
23
|
+
)
|
|
24
|
+
from pythinker_code.soul.pythinkersoul import PythinkerSoul
|
|
25
|
+
from pythinker_code.soul.toolset import PythinkerToolset, WireExternalTool
|
|
26
|
+
from pythinker_code.utils.aioqueue import Queue, QueueShutDown
|
|
27
|
+
from pythinker_code.utils.logging import logger
|
|
28
|
+
from pythinker_code.utils.signals import install_sigint_handler
|
|
29
|
+
from pythinker_code.wire import Wire
|
|
30
|
+
from pythinker_code.wire.types import (
|
|
31
|
+
ApprovalRequest,
|
|
32
|
+
ApprovalResponse,
|
|
33
|
+
HookRequest,
|
|
34
|
+
HookResponse,
|
|
35
|
+
QuestionNotSupported,
|
|
36
|
+
QuestionRequest,
|
|
37
|
+
QuestionResponse,
|
|
38
|
+
Request,
|
|
39
|
+
StatusUpdate,
|
|
40
|
+
ToolCallRequest,
|
|
41
|
+
is_event,
|
|
42
|
+
is_request,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
from .jsonrpc import (
|
|
46
|
+
ClientInfo,
|
|
47
|
+
ErrorCodes,
|
|
48
|
+
JSONRPCCancelMessage,
|
|
49
|
+
JSONRPCErrorObject,
|
|
50
|
+
JSONRPCErrorResponse,
|
|
51
|
+
JSONRPCErrorResponseNullableID,
|
|
52
|
+
JSONRPCEventMessage,
|
|
53
|
+
JSONRPCInitializeMessage,
|
|
54
|
+
JSONRPCInMessage,
|
|
55
|
+
JSONRPCInMessageAdapter,
|
|
56
|
+
JSONRPCMessage,
|
|
57
|
+
JSONRPCOutMessage,
|
|
58
|
+
JSONRPCPromptMessage,
|
|
59
|
+
JSONRPCReplayMessage,
|
|
60
|
+
JSONRPCRequestMessage,
|
|
61
|
+
JSONRPCSetPlanModeMessage,
|
|
62
|
+
JSONRPCSteerMessage,
|
|
63
|
+
JSONRPCSuccessResponse,
|
|
64
|
+
Statuses,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Maximum buffer size for the asyncio StreamReader used for stdio.
|
|
68
|
+
# Passed as the `limit` argument to `acp.stdio_streams`, this caps how much
|
|
69
|
+
# data can be buffered when reading from stdin (e.g., large tool or model
|
|
70
|
+
# outputs sent over JSON-RPC). A 100MB limit is large enough for typical
|
|
71
|
+
# interactive use while still protecting the process from unbounded memory
|
|
72
|
+
# growth or buffer-overrun errors when peers send unexpectedly large payloads.
|
|
73
|
+
STDIO_BUFFER_LIMIT = 100 * 1024 * 1024
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _is_oauth_session(runtime: Any) -> bool:
|
|
77
|
+
"""Return True if the current session uses OAuth-based authentication."""
|
|
78
|
+
if runtime is None:
|
|
79
|
+
return False
|
|
80
|
+
llm = getattr(runtime, "llm", None)
|
|
81
|
+
if llm is None:
|
|
82
|
+
return False
|
|
83
|
+
provider_config = getattr(llm, "provider_config", None)
|
|
84
|
+
if provider_config is None:
|
|
85
|
+
return False
|
|
86
|
+
return getattr(provider_config, "oauth", None) is not None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class WireServer:
|
|
90
|
+
def __init__(self, soul: Soul):
|
|
91
|
+
self._reader: asyncio.StreamReader | None = None
|
|
92
|
+
self._writer: asyncio.StreamWriter | None = None
|
|
93
|
+
|
|
94
|
+
# outward
|
|
95
|
+
self._write_task: asyncio.Task[None] | None = None
|
|
96
|
+
self._write_queue: Queue[JSONRPCOutMessage] = Queue()
|
|
97
|
+
|
|
98
|
+
# inward
|
|
99
|
+
self._dispatch_tasks: set[asyncio.Task[None]] = set()
|
|
100
|
+
|
|
101
|
+
# soul running stuffs
|
|
102
|
+
self._soul = soul
|
|
103
|
+
self._cancel_event: asyncio.Event | None = None
|
|
104
|
+
self._pending_requests: dict[str, Request] = {}
|
|
105
|
+
"""Maps JSON RPC message IDs to pending `Request`s."""
|
|
106
|
+
self._client_supports_question: bool = False
|
|
107
|
+
"""Whether the Wire client supports QuestionRequest."""
|
|
108
|
+
self._client_supports_plan_mode: bool = False
|
|
109
|
+
"""Whether the Wire client supports plan mode."""
|
|
110
|
+
self._initialized: bool = False
|
|
111
|
+
self._root_hub_queue: Queue[Any] | None = None
|
|
112
|
+
self._root_hub_task: asyncio.Task[None] | None = None
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def _approval_runtime(self) -> ApprovalRuntime | None:
|
|
116
|
+
if isinstance(self._soul, PythinkerSoul):
|
|
117
|
+
return self._soul.runtime.approval_runtime
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
async def serve(self) -> None:
|
|
121
|
+
logger.info("Starting Wire server on stdio")
|
|
122
|
+
|
|
123
|
+
self._reader, self._writer = await acp.stdio_streams(limit=STDIO_BUFFER_LIMIT)
|
|
124
|
+
self._write_task = asyncio.create_task(self._write_loop())
|
|
125
|
+
if isinstance(self._soul, PythinkerSoul) and self._soul.runtime.root_wire_hub is not None:
|
|
126
|
+
self._root_hub_queue = self._soul.runtime.root_wire_hub.subscribe()
|
|
127
|
+
self._root_hub_task = asyncio.create_task(self._root_hub_loop())
|
|
128
|
+
stop_event = asyncio.Event()
|
|
129
|
+
loop = asyncio.get_running_loop()
|
|
130
|
+
remove_sigint = install_sigint_handler(loop, stop_event.set)
|
|
131
|
+
read_task = asyncio.create_task(self._read_loop())
|
|
132
|
+
stop_task = asyncio.create_task(stop_event.wait())
|
|
133
|
+
tasks: set[asyncio.Task[Any]] = {read_task, stop_task}
|
|
134
|
+
pending = tasks
|
|
135
|
+
try:
|
|
136
|
+
done, pending = await asyncio.wait(
|
|
137
|
+
tasks,
|
|
138
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
139
|
+
)
|
|
140
|
+
if stop_event.is_set():
|
|
141
|
+
logger.info("Wire server interrupted, shutting down")
|
|
142
|
+
if self._cancel_event is not None:
|
|
143
|
+
self._cancel_event.set()
|
|
144
|
+
if not read_task.done():
|
|
145
|
+
read_task.cancel()
|
|
146
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
147
|
+
await read_task
|
|
148
|
+
elif read_task in done:
|
|
149
|
+
read_task.result()
|
|
150
|
+
except KeyboardInterrupt:
|
|
151
|
+
logger.info("Wire server interrupted, shutting down")
|
|
152
|
+
if self._cancel_event is not None:
|
|
153
|
+
self._cancel_event.set()
|
|
154
|
+
finally:
|
|
155
|
+
remove_sigint()
|
|
156
|
+
for task in pending:
|
|
157
|
+
task.cancel()
|
|
158
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
159
|
+
await task
|
|
160
|
+
await self._shutdown()
|
|
161
|
+
|
|
162
|
+
async def _root_hub_loop(self) -> None:
|
|
163
|
+
assert self._root_hub_queue is not None
|
|
164
|
+
while True:
|
|
165
|
+
try:
|
|
166
|
+
msg = await self._root_hub_queue.get()
|
|
167
|
+
except QueueShutDown:
|
|
168
|
+
return
|
|
169
|
+
try:
|
|
170
|
+
if not self._initialized:
|
|
171
|
+
continue
|
|
172
|
+
if isinstance(msg, ApprovalRequest):
|
|
173
|
+
await self._request_approval(msg)
|
|
174
|
+
elif isinstance(msg, ApprovalResponse):
|
|
175
|
+
self._pending_requests.pop(msg.request_id, None)
|
|
176
|
+
await self._send_msg(JSONRPCEventMessage(method="event", params=msg))
|
|
177
|
+
elif is_event(msg):
|
|
178
|
+
await self._send_msg(JSONRPCEventMessage(method="event", params=msg))
|
|
179
|
+
except Exception:
|
|
180
|
+
logger.exception("Root hub message handling failed")
|
|
181
|
+
|
|
182
|
+
async def _write_loop(self) -> None:
|
|
183
|
+
assert self._writer is not None
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
while True:
|
|
187
|
+
try:
|
|
188
|
+
msg = await self._write_queue.get()
|
|
189
|
+
except QueueShutDown:
|
|
190
|
+
logger.debug("Send queue shut down, stopping Wire server write loop")
|
|
191
|
+
break
|
|
192
|
+
self._writer.write(msg.model_dump_json().encode("utf-8") + b"\n")
|
|
193
|
+
await self._writer.drain()
|
|
194
|
+
except asyncio.CancelledError:
|
|
195
|
+
raise
|
|
196
|
+
except Exception:
|
|
197
|
+
logger.exception("Wire server write loop error:")
|
|
198
|
+
raise
|
|
199
|
+
|
|
200
|
+
async def _read_loop(self) -> None:
|
|
201
|
+
assert self._reader is not None
|
|
202
|
+
|
|
203
|
+
while True:
|
|
204
|
+
raw_line = await self._reader.readline()
|
|
205
|
+
if not raw_line:
|
|
206
|
+
logger.info("stdin closed, Wire server exiting")
|
|
207
|
+
break
|
|
208
|
+
line = raw_line.decode("utf-8", errors="replace").strip()
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
msg_json = json.loads(line)
|
|
212
|
+
except ValueError:
|
|
213
|
+
logger.error("Invalid JSON line: {line}", line=line)
|
|
214
|
+
await self._send_msg(
|
|
215
|
+
JSONRPCErrorResponseNullableID(
|
|
216
|
+
id=None,
|
|
217
|
+
error=JSONRPCErrorObject(
|
|
218
|
+
code=ErrorCodes.PARSE_ERROR,
|
|
219
|
+
message="Invalid JSON format",
|
|
220
|
+
),
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
generic_msg = JSONRPCMessage.model_validate(msg_json)
|
|
227
|
+
except pydantic.ValidationError as e:
|
|
228
|
+
logger.error("Invalid JSON-RPC message: {error}", error=e)
|
|
229
|
+
await self._send_msg(
|
|
230
|
+
JSONRPCErrorResponseNullableID(
|
|
231
|
+
id=None,
|
|
232
|
+
error=JSONRPCErrorObject(
|
|
233
|
+
code=ErrorCodes.INVALID_REQUEST,
|
|
234
|
+
message="Invalid request",
|
|
235
|
+
),
|
|
236
|
+
)
|
|
237
|
+
)
|
|
238
|
+
continue
|
|
239
|
+
|
|
240
|
+
if generic_msg.is_response():
|
|
241
|
+
# for responses, we skip the method check
|
|
242
|
+
try:
|
|
243
|
+
msg = JSONRPCInMessageAdapter.validate_python(msg_json)
|
|
244
|
+
except pydantic.ValidationError as e:
|
|
245
|
+
logger.error("Invalid JSON-RPC response: {error}", error=e)
|
|
246
|
+
await self._send_msg(
|
|
247
|
+
JSONRPCErrorResponseNullableID(
|
|
248
|
+
id=None,
|
|
249
|
+
error=JSONRPCErrorObject(
|
|
250
|
+
code=ErrorCodes.INVALID_REQUEST,
|
|
251
|
+
message="Invalid response",
|
|
252
|
+
),
|
|
253
|
+
)
|
|
254
|
+
)
|
|
255
|
+
continue # ignore invalid json-rpc responses
|
|
256
|
+
|
|
257
|
+
if not isinstance(msg, (JSONRPCSuccessResponse, JSONRPCErrorResponse)):
|
|
258
|
+
logger.error(
|
|
259
|
+
"Invalid JSON-RPC response message: {msg}",
|
|
260
|
+
msg=msg_json,
|
|
261
|
+
)
|
|
262
|
+
continue # ignore invalid response messages
|
|
263
|
+
|
|
264
|
+
task = asyncio.create_task(self._dispatch_msg(msg))
|
|
265
|
+
task.add_done_callback(self._dispatch_tasks.discard)
|
|
266
|
+
self._dispatch_tasks.add(task)
|
|
267
|
+
continue
|
|
268
|
+
|
|
269
|
+
if not generic_msg.method_is_inbound():
|
|
270
|
+
logger.error(
|
|
271
|
+
"Unexpected JSON-RPC method received: {method}",
|
|
272
|
+
method=generic_msg.method,
|
|
273
|
+
)
|
|
274
|
+
if generic_msg.id is not None:
|
|
275
|
+
resp = JSONRPCErrorResponse(
|
|
276
|
+
id=generic_msg.id,
|
|
277
|
+
error=JSONRPCErrorObject(
|
|
278
|
+
code=ErrorCodes.METHOD_NOT_FOUND,
|
|
279
|
+
message=f"Unexpected method received: {generic_msg.method}",
|
|
280
|
+
),
|
|
281
|
+
)
|
|
282
|
+
await self._send_msg(resp)
|
|
283
|
+
continue # ignore unexpected outbound methods
|
|
284
|
+
|
|
285
|
+
try:
|
|
286
|
+
msg = JSONRPCInMessageAdapter.validate_python(msg_json)
|
|
287
|
+
except pydantic.ValidationError as e:
|
|
288
|
+
logger.error("Invalid JSON-RPC inbound message: {error}", error=e)
|
|
289
|
+
if generic_msg.id is not None:
|
|
290
|
+
resp = JSONRPCErrorResponse(
|
|
291
|
+
id=generic_msg.id,
|
|
292
|
+
error=JSONRPCErrorObject(
|
|
293
|
+
code=ErrorCodes.INVALID_PARAMS,
|
|
294
|
+
message=f"Invalid parameters for method `{generic_msg.method}`",
|
|
295
|
+
),
|
|
296
|
+
)
|
|
297
|
+
await self._send_msg(resp)
|
|
298
|
+
continue # ignore invalid inbound messages
|
|
299
|
+
|
|
300
|
+
task = asyncio.create_task(self._dispatch_msg(msg))
|
|
301
|
+
task.add_done_callback(self._dispatch_tasks.discard)
|
|
302
|
+
self._dispatch_tasks.add(task)
|
|
303
|
+
|
|
304
|
+
async def _shutdown(self) -> None:
|
|
305
|
+
for request in self._pending_requests.values():
|
|
306
|
+
if request.resolved:
|
|
307
|
+
continue
|
|
308
|
+
match request:
|
|
309
|
+
case ApprovalRequest():
|
|
310
|
+
if request.source_kind == "foreground_turn":
|
|
311
|
+
request.resolve("reject")
|
|
312
|
+
if self._approval_runtime is not None:
|
|
313
|
+
self._approval_runtime.resolve(request.id, "reject")
|
|
314
|
+
case ToolCallRequest():
|
|
315
|
+
request.resolve(
|
|
316
|
+
ToolError(
|
|
317
|
+
message="Wire connection closed before tool result was received.",
|
|
318
|
+
brief="Wire closed",
|
|
319
|
+
)
|
|
320
|
+
)
|
|
321
|
+
case QuestionRequest():
|
|
322
|
+
request.resolve({})
|
|
323
|
+
case HookRequest():
|
|
324
|
+
request.resolve("allow")
|
|
325
|
+
self._pending_requests.clear()
|
|
326
|
+
|
|
327
|
+
if self._cancel_event is not None:
|
|
328
|
+
self._cancel_event.set()
|
|
329
|
+
self._cancel_event = None
|
|
330
|
+
|
|
331
|
+
self._write_queue.shutdown()
|
|
332
|
+
if self._write_task is not None:
|
|
333
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
334
|
+
await self._write_task
|
|
335
|
+
|
|
336
|
+
if self._root_hub_task is not None:
|
|
337
|
+
self._root_hub_task.cancel()
|
|
338
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
339
|
+
await self._root_hub_task
|
|
340
|
+
self._root_hub_task = None
|
|
341
|
+
if (
|
|
342
|
+
isinstance(self._soul, PythinkerSoul)
|
|
343
|
+
and self._root_hub_queue is not None
|
|
344
|
+
and self._soul.runtime.root_wire_hub is not None
|
|
345
|
+
):
|
|
346
|
+
self._soul.runtime.root_wire_hub.unsubscribe(self._root_hub_queue)
|
|
347
|
+
self._root_hub_queue = None
|
|
348
|
+
|
|
349
|
+
await asyncio.gather(*self._dispatch_tasks, return_exceptions=True)
|
|
350
|
+
self._dispatch_tasks.clear()
|
|
351
|
+
|
|
352
|
+
if self._writer is not None:
|
|
353
|
+
self._writer.close()
|
|
354
|
+
with contextlib.suppress(Exception):
|
|
355
|
+
await self._writer.wait_closed()
|
|
356
|
+
self._writer = None
|
|
357
|
+
|
|
358
|
+
self._reader = None
|
|
359
|
+
self._initialized = False
|
|
360
|
+
|
|
361
|
+
async def _dispatch_msg(self, msg: JSONRPCInMessage) -> None:
|
|
362
|
+
resp: JSONRPCSuccessResponse | JSONRPCErrorResponse | None = None
|
|
363
|
+
try:
|
|
364
|
+
match msg:
|
|
365
|
+
case JSONRPCInitializeMessage():
|
|
366
|
+
resp = await self._handle_initialize(msg)
|
|
367
|
+
case JSONRPCPromptMessage():
|
|
368
|
+
resp = await self._handle_prompt(msg)
|
|
369
|
+
case JSONRPCReplayMessage():
|
|
370
|
+
resp = await self._handle_replay(msg)
|
|
371
|
+
case JSONRPCSteerMessage():
|
|
372
|
+
resp = await self._handle_steer(msg)
|
|
373
|
+
case JSONRPCSetPlanModeMessage():
|
|
374
|
+
resp = await self._handle_set_plan_mode(msg)
|
|
375
|
+
case JSONRPCCancelMessage():
|
|
376
|
+
resp = await self._handle_cancel(msg)
|
|
377
|
+
case JSONRPCSuccessResponse() | JSONRPCErrorResponse():
|
|
378
|
+
await self._handle_response(msg)
|
|
379
|
+
|
|
380
|
+
if resp is not None:
|
|
381
|
+
await self._send_msg(resp)
|
|
382
|
+
except Exception:
|
|
383
|
+
logger.exception("Unexpected error dispatching JSONRPC message:")
|
|
384
|
+
raise
|
|
385
|
+
|
|
386
|
+
async def _send_msg(self, msg: JSONRPCOutMessage) -> None:
|
|
387
|
+
try:
|
|
388
|
+
await self._write_queue.put(msg)
|
|
389
|
+
except QueueShutDown:
|
|
390
|
+
logger.error("Send queue shut down; dropping message: {msg}", msg=msg)
|
|
391
|
+
|
|
392
|
+
@property
|
|
393
|
+
def _is_streaming(self) -> bool:
|
|
394
|
+
return self._cancel_event is not None
|
|
395
|
+
|
|
396
|
+
async def _handle_initialize(
|
|
397
|
+
self, msg: JSONRPCInitializeMessage
|
|
398
|
+
) -> JSONRPCSuccessResponse | JSONRPCErrorResponse:
|
|
399
|
+
if self._is_streaming:
|
|
400
|
+
return JSONRPCErrorResponse(
|
|
401
|
+
id=msg.id,
|
|
402
|
+
error=JSONRPCErrorObject(
|
|
403
|
+
code=ErrorCodes.INVALID_STATE,
|
|
404
|
+
message="An agent turn is already in progress",
|
|
405
|
+
),
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
accepted: list[str] = []
|
|
409
|
+
rejected: list[dict[str, str]] = []
|
|
410
|
+
toolset = None
|
|
411
|
+
if isinstance(self._soul, PythinkerSoul) and isinstance(
|
|
412
|
+
self._soul.agent.toolset, PythinkerToolset
|
|
413
|
+
):
|
|
414
|
+
toolset = self._soul.agent.toolset
|
|
415
|
+
|
|
416
|
+
if toolset and msg.params.external_tools:
|
|
417
|
+
for tool in msg.params.external_tools:
|
|
418
|
+
existing = toolset.find(tool.name)
|
|
419
|
+
if existing is not None and not isinstance(existing, WireExternalTool):
|
|
420
|
+
rejected.append({"name": tool.name, "reason": "conflicts with builtin tool"})
|
|
421
|
+
continue
|
|
422
|
+
ok, reason = toolset.register_external_tool(
|
|
423
|
+
tool.name,
|
|
424
|
+
tool.description,
|
|
425
|
+
tool.parameters,
|
|
426
|
+
)
|
|
427
|
+
if ok:
|
|
428
|
+
accepted.append(tool.name)
|
|
429
|
+
else:
|
|
430
|
+
rejected.append({"name": tool.name, "reason": reason or "invalid schema"})
|
|
431
|
+
|
|
432
|
+
slash_commands: list[JsonType] = []
|
|
433
|
+
for cmd in self._soul.available_slash_commands:
|
|
434
|
+
slash_commands.append(
|
|
435
|
+
cast(
|
|
436
|
+
JsonType,
|
|
437
|
+
{"name": cmd.name, "description": cmd.description, "aliases": cmd.aliases},
|
|
438
|
+
)
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
from pythinker_code.constant import NAME, VERSION
|
|
442
|
+
from pythinker_code.hooks.config import HOOK_EVENT_TYPES
|
|
443
|
+
from pythinker_code.hooks.engine import WireHookHandle, WireHookSubscription
|
|
444
|
+
from pythinker_code.soul import wire_send
|
|
445
|
+
from pythinker_code.wire.protocol import WIRE_PROTOCOL_VERSION
|
|
446
|
+
from pythinker_code.wire.types import HookResolved, HookTriggered
|
|
447
|
+
|
|
448
|
+
# Hook engine setup — register wire subscriptions and callbacks
|
|
449
|
+
|
|
450
|
+
hook_engine = self._soul.hook_engine
|
|
451
|
+
|
|
452
|
+
if msg.params.hooks:
|
|
453
|
+
wire_subs: list[WireHookSubscription] = []
|
|
454
|
+
for wh in msg.params.hooks:
|
|
455
|
+
if wh.event not in HOOK_EVENT_TYPES:
|
|
456
|
+
logger.warning("Ignoring unknown hook event from client: {}", wh.event)
|
|
457
|
+
continue
|
|
458
|
+
wire_subs.append(
|
|
459
|
+
WireHookSubscription(
|
|
460
|
+
id=wh.id,
|
|
461
|
+
event=wh.event,
|
|
462
|
+
matcher=wh.matcher,
|
|
463
|
+
timeout=wh.timeout,
|
|
464
|
+
)
|
|
465
|
+
)
|
|
466
|
+
if wire_subs:
|
|
467
|
+
hook_engine.add_wire_subscriptions(wire_subs)
|
|
468
|
+
logger.info("Registered {} wire hook subscriptions from client", len(wire_subs))
|
|
469
|
+
|
|
470
|
+
def _on_triggered(event: str, target: str, count: int) -> None:
|
|
471
|
+
wire_send(HookTriggered(event=event, target=target, hook_count=count))
|
|
472
|
+
|
|
473
|
+
def _on_resolved(
|
|
474
|
+
event: str,
|
|
475
|
+
target: str,
|
|
476
|
+
action: str,
|
|
477
|
+
reason: str,
|
|
478
|
+
duration_ms: int,
|
|
479
|
+
) -> None:
|
|
480
|
+
wire_send(
|
|
481
|
+
HookResolved(
|
|
482
|
+
event=event,
|
|
483
|
+
target=target,
|
|
484
|
+
action=cast(Literal["allow", "block"], action),
|
|
485
|
+
reason=reason,
|
|
486
|
+
duration_ms=duration_ms,
|
|
487
|
+
)
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
async def _on_wire_hook(handle: WireHookHandle) -> None:
|
|
491
|
+
"""Send HookRequest to client, wire response back to handle."""
|
|
492
|
+
request = HookRequest(
|
|
493
|
+
id=handle.id,
|
|
494
|
+
subscription_id=handle.subscription_id,
|
|
495
|
+
event=handle.event,
|
|
496
|
+
target=handle.target,
|
|
497
|
+
input_data=handle.input_data,
|
|
498
|
+
)
|
|
499
|
+
self._pending_requests[handle.id] = request
|
|
500
|
+
await self._send_msg(JSONRPCRequestMessage(id=handle.id, params=request))
|
|
501
|
+
# Wait for client response (resolved via _handle_response)
|
|
502
|
+
action, reason = await request.wait()
|
|
503
|
+
handle.resolve(action, reason)
|
|
504
|
+
|
|
505
|
+
hook_engine.set_callbacks(
|
|
506
|
+
on_triggered=_on_triggered,
|
|
507
|
+
on_resolved=_on_resolved,
|
|
508
|
+
on_wire_hook=_on_wire_hook,
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
hooks_info: dict[str, JsonType] = cast(
|
|
512
|
+
dict[str, JsonType],
|
|
513
|
+
{
|
|
514
|
+
"supported_events": HOOK_EVENT_TYPES,
|
|
515
|
+
"configured": hook_engine.summary,
|
|
516
|
+
},
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
result: dict[str, JsonType] = {
|
|
520
|
+
"protocol_version": WIRE_PROTOCOL_VERSION,
|
|
521
|
+
"server": cast(JsonType, {"name": NAME, "version": VERSION}),
|
|
522
|
+
"slash_commands": cast(JsonType, slash_commands),
|
|
523
|
+
}
|
|
524
|
+
if accepted or rejected:
|
|
525
|
+
result["external_tools"] = cast(
|
|
526
|
+
JsonType,
|
|
527
|
+
{
|
|
528
|
+
"accepted": accepted,
|
|
529
|
+
"rejected": rejected,
|
|
530
|
+
},
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
if hooks_info:
|
|
534
|
+
result["hooks"] = cast(JsonType, hooks_info)
|
|
535
|
+
|
|
536
|
+
self._apply_wire_client_info(msg.params.client)
|
|
537
|
+
self._track_session_started(msg.params.client)
|
|
538
|
+
|
|
539
|
+
if msg.params.capabilities is not None:
|
|
540
|
+
self._client_supports_question = msg.params.capabilities.supports_question
|
|
541
|
+
self._client_supports_plan_mode = msg.params.capabilities.supports_plan_mode
|
|
542
|
+
|
|
543
|
+
if toolset is not None:
|
|
544
|
+
self._sync_ask_user_tool_visibility(toolset)
|
|
545
|
+
self._sync_plan_mode_tool_visibility(toolset)
|
|
546
|
+
|
|
547
|
+
self._initialized = True
|
|
548
|
+
if self._approval_runtime is not None:
|
|
549
|
+
for request in self._approval_runtime.list_pending():
|
|
550
|
+
await self._request_approval(
|
|
551
|
+
ApprovalRequest(
|
|
552
|
+
id=request.id,
|
|
553
|
+
tool_call_id=request.tool_call_id,
|
|
554
|
+
sender=request.sender,
|
|
555
|
+
action=request.action,
|
|
556
|
+
description=request.description,
|
|
557
|
+
display=request.display,
|
|
558
|
+
source_kind=request.source.kind,
|
|
559
|
+
source_id=request.source.id,
|
|
560
|
+
agent_id=request.source.agent_id,
|
|
561
|
+
subagent_type=request.source.subagent_type,
|
|
562
|
+
)
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
result["capabilities"] = cast(
|
|
566
|
+
JsonType,
|
|
567
|
+
{"supports_question": True},
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
return JSONRPCSuccessResponse(
|
|
571
|
+
id=msg.id,
|
|
572
|
+
result=result,
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
def _sync_ask_user_tool_visibility(self, toolset: PythinkerToolset) -> None:
|
|
576
|
+
"""Hide or unhide the AskUserQuestion tool based on client capabilities."""
|
|
577
|
+
from pythinker_code.tools.ask_user import NAME as ASK_USER_TOOL_NAME
|
|
578
|
+
|
|
579
|
+
all_toolsets = [toolset]
|
|
580
|
+
|
|
581
|
+
if self._client_supports_question:
|
|
582
|
+
for ts in all_toolsets:
|
|
583
|
+
ts.unhide(ASK_USER_TOOL_NAME)
|
|
584
|
+
else:
|
|
585
|
+
for ts in all_toolsets:
|
|
586
|
+
ts.hide(ASK_USER_TOOL_NAME)
|
|
587
|
+
logger.info(
|
|
588
|
+
"Hid {tool} tool: client does not support questions",
|
|
589
|
+
tool=ASK_USER_TOOL_NAME,
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
def _sync_plan_mode_tool_visibility(self, toolset: PythinkerToolset) -> None:
|
|
593
|
+
"""Hide or unhide plan mode tools based on client capabilities."""
|
|
594
|
+
from pythinker_code.tools.plan import NAME as EXIT_PLAN_MODE_TOOL_NAME
|
|
595
|
+
from pythinker_code.tools.plan.enter import NAME as ENTER_PLAN_MODE_TOOL_NAME
|
|
596
|
+
|
|
597
|
+
plan_tool_names = [ENTER_PLAN_MODE_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME]
|
|
598
|
+
|
|
599
|
+
all_toolsets = [toolset]
|
|
600
|
+
|
|
601
|
+
if self._client_supports_plan_mode:
|
|
602
|
+
for ts in all_toolsets:
|
|
603
|
+
for name in plan_tool_names:
|
|
604
|
+
ts.unhide(name)
|
|
605
|
+
else:
|
|
606
|
+
for ts in all_toolsets:
|
|
607
|
+
for name in plan_tool_names:
|
|
608
|
+
ts.hide(name)
|
|
609
|
+
logger.info(
|
|
610
|
+
"Hide plan mode tools: client does not support plan mode",
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
def _apply_wire_client_info(self, client: ClientInfo | None) -> None:
|
|
614
|
+
if client is not None:
|
|
615
|
+
from pythinker_code.telemetry import set_client_info
|
|
616
|
+
|
|
617
|
+
set_client_info(name=client.name, version=client.version)
|
|
618
|
+
|
|
619
|
+
if not isinstance(self._soul, PythinkerSoul):
|
|
620
|
+
return
|
|
621
|
+
llm = self._soul.runtime.llm
|
|
622
|
+
if llm is None:
|
|
623
|
+
return
|
|
624
|
+
|
|
625
|
+
ua_suffix = ""
|
|
626
|
+
if client is not None:
|
|
627
|
+
ua_suffix = client.name
|
|
628
|
+
if client.version:
|
|
629
|
+
ua_suffix += f" {client.version}"
|
|
630
|
+
ua_suffix = f" ({ua_suffix.strip()})"
|
|
631
|
+
|
|
632
|
+
from pythinker_core.chat_provider.pythinker import Pythinker
|
|
633
|
+
|
|
634
|
+
if isinstance(llm.chat_provider, Pythinker):
|
|
635
|
+
pythinker_codeent = llm.chat_provider.client
|
|
636
|
+
headers = dict(pythinker_codeent._custom_headers) # pyright: ignore[reportPrivateUsage]
|
|
637
|
+
headers["User-Agent"] = f"{USER_AGENT}{ua_suffix}"
|
|
638
|
+
pythinker_codeent._custom_headers = headers # pyright: ignore[reportPrivateUsage]
|
|
639
|
+
|
|
640
|
+
def _track_session_started(self, client: ClientInfo | None) -> None:
|
|
641
|
+
if not isinstance(self._soul, PythinkerSoul):
|
|
642
|
+
return
|
|
643
|
+
|
|
644
|
+
from pythinker_code.telemetry import track_session_started_once
|
|
645
|
+
|
|
646
|
+
track_session_started_once(
|
|
647
|
+
ui_mode="wire",
|
|
648
|
+
resumed=self._soul.runtime.resumed,
|
|
649
|
+
client_name=client.name if client is not None else None,
|
|
650
|
+
client_version=client.version if client is not None else None,
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
async def _handle_prompt(
|
|
654
|
+
self, msg: JSONRPCPromptMessage
|
|
655
|
+
) -> JSONRPCSuccessResponse | JSONRPCErrorResponse:
|
|
656
|
+
if self._is_streaming:
|
|
657
|
+
# TODO: support queueing multiple inputs
|
|
658
|
+
return JSONRPCErrorResponse(
|
|
659
|
+
id=msg.id,
|
|
660
|
+
error=JSONRPCErrorObject(
|
|
661
|
+
code=ErrorCodes.INVALID_STATE, message="An agent turn is already in progress"
|
|
662
|
+
),
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
if not self._initialized:
|
|
666
|
+
self._track_session_started(None)
|
|
667
|
+
|
|
668
|
+
self._cancel_event = asyncio.Event()
|
|
669
|
+
runtime = self._soul.runtime if isinstance(self._soul, PythinkerSoul) else None
|
|
670
|
+
try:
|
|
671
|
+
await run_soul(
|
|
672
|
+
self._soul,
|
|
673
|
+
msg.params.user_input,
|
|
674
|
+
self._stream_wire_messages,
|
|
675
|
+
self._cancel_event,
|
|
676
|
+
runtime.session.wire_file if runtime else None,
|
|
677
|
+
runtime,
|
|
678
|
+
)
|
|
679
|
+
return JSONRPCSuccessResponse(
|
|
680
|
+
id=msg.id,
|
|
681
|
+
result={"status": Statuses.FINISHED},
|
|
682
|
+
)
|
|
683
|
+
except LLMNotSet:
|
|
684
|
+
return JSONRPCErrorResponse(
|
|
685
|
+
id=msg.id,
|
|
686
|
+
error=JSONRPCErrorObject(code=ErrorCodes.LLM_NOT_SET, message="LLM is not set"),
|
|
687
|
+
)
|
|
688
|
+
except LLMNotSupported as e:
|
|
689
|
+
return JSONRPCErrorResponse(
|
|
690
|
+
id=msg.id,
|
|
691
|
+
error=JSONRPCErrorObject(code=ErrorCodes.LLM_NOT_SUPPORTED, message=str(e)),
|
|
692
|
+
)
|
|
693
|
+
except APIStatusError as e:
|
|
694
|
+
if e.status_code == 401 and _is_oauth_session(runtime):
|
|
695
|
+
return JSONRPCErrorResponse(
|
|
696
|
+
id=msg.id,
|
|
697
|
+
error=JSONRPCErrorObject(
|
|
698
|
+
code=ErrorCodes.AUTH_EXPIRED,
|
|
699
|
+
message=(
|
|
700
|
+
"Authentication failed. Your login session may have expired. "
|
|
701
|
+
'Please run "/login" to sign in again.'
|
|
702
|
+
),
|
|
703
|
+
),
|
|
704
|
+
)
|
|
705
|
+
return JSONRPCErrorResponse(
|
|
706
|
+
id=msg.id,
|
|
707
|
+
error=JSONRPCErrorObject(code=ErrorCodes.CHAT_PROVIDER_ERROR, message=str(e)),
|
|
708
|
+
)
|
|
709
|
+
except ChatProviderError as e:
|
|
710
|
+
return JSONRPCErrorResponse(
|
|
711
|
+
id=msg.id,
|
|
712
|
+
error=JSONRPCErrorObject(code=ErrorCodes.CHAT_PROVIDER_ERROR, message=str(e)),
|
|
713
|
+
)
|
|
714
|
+
except MaxStepsReached as e:
|
|
715
|
+
return JSONRPCSuccessResponse(
|
|
716
|
+
id=msg.id,
|
|
717
|
+
result={"status": Statuses.MAX_STEPS_REACHED, "steps": e.n_steps},
|
|
718
|
+
)
|
|
719
|
+
except RunCancelled:
|
|
720
|
+
return JSONRPCSuccessResponse(
|
|
721
|
+
id=msg.id,
|
|
722
|
+
result={"status": Statuses.CANCELLED},
|
|
723
|
+
)
|
|
724
|
+
except Exception as e:
|
|
725
|
+
logger.exception("Unexpected error in prompt handler")
|
|
726
|
+
return JSONRPCErrorResponse(
|
|
727
|
+
id=msg.id,
|
|
728
|
+
error=JSONRPCErrorObject(
|
|
729
|
+
code=ErrorCodes.INTERNAL_ERROR,
|
|
730
|
+
message=f"{type(e).__name__}: {e}",
|
|
731
|
+
),
|
|
732
|
+
)
|
|
733
|
+
finally:
|
|
734
|
+
# Clean up any remaining pending requests from this turn.
|
|
735
|
+
# After run_soul() returns, the soul and all subagents are done,
|
|
736
|
+
# so any unresolved requests are stale.
|
|
737
|
+
stale_ids = [k for k, v in self._pending_requests.items() if not v.resolved]
|
|
738
|
+
for msg_id in stale_ids:
|
|
739
|
+
request = self._pending_requests[msg_id]
|
|
740
|
+
match request:
|
|
741
|
+
case ApprovalRequest():
|
|
742
|
+
if request.source_kind == "foreground_turn":
|
|
743
|
+
self._pending_requests.pop(msg_id, None)
|
|
744
|
+
request.resolve("reject")
|
|
745
|
+
if self._approval_runtime is not None:
|
|
746
|
+
self._approval_runtime.resolve(request.id, "reject")
|
|
747
|
+
case ToolCallRequest():
|
|
748
|
+
self._pending_requests.pop(msg_id, None)
|
|
749
|
+
request.resolve(
|
|
750
|
+
ToolError(
|
|
751
|
+
message="Agent turn ended before tool result was received.",
|
|
752
|
+
brief="Turn ended",
|
|
753
|
+
)
|
|
754
|
+
)
|
|
755
|
+
case QuestionRequest():
|
|
756
|
+
self._pending_requests.pop(msg_id, None)
|
|
757
|
+
request.resolve({})
|
|
758
|
+
case HookRequest():
|
|
759
|
+
self._pending_requests.pop(msg_id, None)
|
|
760
|
+
request.resolve("allow")
|
|
761
|
+
case _:
|
|
762
|
+
pass
|
|
763
|
+
self._cancel_event = None
|
|
764
|
+
|
|
765
|
+
async def _handle_steer(
|
|
766
|
+
self, msg: JSONRPCSteerMessage
|
|
767
|
+
) -> JSONRPCSuccessResponse | JSONRPCErrorResponse:
|
|
768
|
+
if not isinstance(self._soul, PythinkerSoul) or not self._is_streaming:
|
|
769
|
+
return JSONRPCErrorResponse(
|
|
770
|
+
id=msg.id,
|
|
771
|
+
error=JSONRPCErrorObject(
|
|
772
|
+
code=ErrorCodes.INVALID_STATE,
|
|
773
|
+
message="No agent turn is in progress",
|
|
774
|
+
),
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
self._soul.steer(msg.params.user_input)
|
|
778
|
+
return JSONRPCSuccessResponse(
|
|
779
|
+
id=msg.id,
|
|
780
|
+
result={"status": Statuses.STEERED},
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
async def _handle_set_plan_mode(
|
|
784
|
+
self, msg: JSONRPCSetPlanModeMessage
|
|
785
|
+
) -> JSONRPCSuccessResponse | JSONRPCErrorResponse:
|
|
786
|
+
if not isinstance(self._soul, PythinkerSoul):
|
|
787
|
+
return JSONRPCErrorResponse(
|
|
788
|
+
id=msg.id,
|
|
789
|
+
error=JSONRPCErrorObject(
|
|
790
|
+
code=ErrorCodes.INVALID_STATE,
|
|
791
|
+
message="Plan mode is not supported",
|
|
792
|
+
),
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
new_state = await self._soul.set_plan_mode_from_manual(msg.params.enabled)
|
|
796
|
+
|
|
797
|
+
status = StatusUpdate(plan_mode=new_state)
|
|
798
|
+
await self._send_msg(JSONRPCEventMessage(params=status))
|
|
799
|
+
# Persist to wire file so replay reconstructs plan mode state
|
|
800
|
+
await self._soul.wire_file.append_message(status)
|
|
801
|
+
return JSONRPCSuccessResponse(
|
|
802
|
+
id=msg.id,
|
|
803
|
+
result={"status": "ok", "plan_mode": new_state},
|
|
804
|
+
)
|
|
805
|
+
|
|
806
|
+
async def _handle_replay(
|
|
807
|
+
self, msg: JSONRPCReplayMessage
|
|
808
|
+
) -> JSONRPCSuccessResponse | JSONRPCErrorResponse:
|
|
809
|
+
if self._is_streaming:
|
|
810
|
+
return JSONRPCErrorResponse(
|
|
811
|
+
id=msg.id,
|
|
812
|
+
error=JSONRPCErrorObject(
|
|
813
|
+
code=ErrorCodes.INVALID_STATE, message="An agent turn is already in progress"
|
|
814
|
+
),
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
wire_file = self._soul.wire_file if isinstance(self._soul, PythinkerSoul) else None
|
|
818
|
+
|
|
819
|
+
self._cancel_event = asyncio.Event()
|
|
820
|
+
events = 0
|
|
821
|
+
requests = 0
|
|
822
|
+
try:
|
|
823
|
+
if wire_file is None or not wire_file.path.exists():
|
|
824
|
+
return JSONRPCSuccessResponse(
|
|
825
|
+
id=msg.id,
|
|
826
|
+
result={"status": Statuses.FINISHED, "events": 0, "requests": 0},
|
|
827
|
+
)
|
|
828
|
+
|
|
829
|
+
async for record in wire_file.iter_records():
|
|
830
|
+
if self._cancel_event.is_set():
|
|
831
|
+
return JSONRPCSuccessResponse(
|
|
832
|
+
id=msg.id,
|
|
833
|
+
result={
|
|
834
|
+
"status": Statuses.CANCELLED,
|
|
835
|
+
"events": events,
|
|
836
|
+
"requests": requests,
|
|
837
|
+
},
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
try:
|
|
841
|
+
wire_msg = record.to_wire_message()
|
|
842
|
+
except Exception:
|
|
843
|
+
logger.exception(
|
|
844
|
+
"Failed to deserialize wire record for replay: {file}",
|
|
845
|
+
file=wire_file.path,
|
|
846
|
+
)
|
|
847
|
+
continue
|
|
848
|
+
|
|
849
|
+
if is_request(wire_msg):
|
|
850
|
+
await self._send_msg(JSONRPCRequestMessage(id=wire_msg.id, params=wire_msg))
|
|
851
|
+
requests += 1
|
|
852
|
+
elif is_event(wire_msg):
|
|
853
|
+
await self._send_msg(JSONRPCEventMessage(params=wire_msg))
|
|
854
|
+
events += 1
|
|
855
|
+
else:
|
|
856
|
+
# Not reachable for valid WireMessage, but keep a guard for corrupted data.
|
|
857
|
+
logger.warning(
|
|
858
|
+
"Skipping non-wire message during replay: {msg}",
|
|
859
|
+
msg=wire_msg,
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
await asyncio.sleep(0) # yield control for cancel handling
|
|
863
|
+
|
|
864
|
+
if self._cancel_event.is_set():
|
|
865
|
+
return JSONRPCSuccessResponse(
|
|
866
|
+
id=msg.id,
|
|
867
|
+
result={
|
|
868
|
+
"status": Statuses.CANCELLED,
|
|
869
|
+
"events": events,
|
|
870
|
+
"requests": requests,
|
|
871
|
+
},
|
|
872
|
+
)
|
|
873
|
+
|
|
874
|
+
return JSONRPCSuccessResponse(
|
|
875
|
+
id=msg.id,
|
|
876
|
+
result={"status": Statuses.FINISHED, "events": events, "requests": requests},
|
|
877
|
+
)
|
|
878
|
+
except Exception:
|
|
879
|
+
logger.exception("Replay failed:")
|
|
880
|
+
return JSONRPCErrorResponse(
|
|
881
|
+
id=msg.id,
|
|
882
|
+
error=JSONRPCErrorObject(
|
|
883
|
+
code=ErrorCodes.INTERNAL_ERROR,
|
|
884
|
+
message="Replay failed",
|
|
885
|
+
),
|
|
886
|
+
)
|
|
887
|
+
finally:
|
|
888
|
+
self._cancel_event = None
|
|
889
|
+
|
|
890
|
+
async def _handle_cancel(
|
|
891
|
+
self, msg: JSONRPCCancelMessage
|
|
892
|
+
) -> JSONRPCSuccessResponse | JSONRPCErrorResponse:
|
|
893
|
+
if not self._is_streaming:
|
|
894
|
+
return JSONRPCErrorResponse(
|
|
895
|
+
id=msg.id,
|
|
896
|
+
error=JSONRPCErrorObject(
|
|
897
|
+
code=ErrorCodes.INVALID_STATE, message="No agent turn is in progress"
|
|
898
|
+
),
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
assert self._cancel_event is not None
|
|
902
|
+
self._cancel_event.set()
|
|
903
|
+
return JSONRPCSuccessResponse(
|
|
904
|
+
id=msg.id,
|
|
905
|
+
result={},
|
|
906
|
+
)
|
|
907
|
+
|
|
908
|
+
async def _handle_response(self, msg: JSONRPCSuccessResponse | JSONRPCErrorResponse) -> None:
|
|
909
|
+
request = self._pending_requests.pop(msg.id, None)
|
|
910
|
+
if request is None:
|
|
911
|
+
logger.error("No pending request for response id={id}", id=msg.id)
|
|
912
|
+
return
|
|
913
|
+
|
|
914
|
+
match request:
|
|
915
|
+
case ApprovalRequest():
|
|
916
|
+
if isinstance(msg, JSONRPCErrorResponse):
|
|
917
|
+
request.resolve("reject")
|
|
918
|
+
if self._approval_runtime is not None:
|
|
919
|
+
self._approval_runtime.resolve(request.id, "reject")
|
|
920
|
+
return
|
|
921
|
+
|
|
922
|
+
try:
|
|
923
|
+
result = ApprovalResponse.model_validate(msg.result)
|
|
924
|
+
except pydantic.ValidationError as e:
|
|
925
|
+
logger.error(
|
|
926
|
+
"Invalid response result for request id={id}: {error}",
|
|
927
|
+
id=msg.id,
|
|
928
|
+
error=e,
|
|
929
|
+
)
|
|
930
|
+
request.resolve("reject")
|
|
931
|
+
if self._approval_runtime is not None:
|
|
932
|
+
self._approval_runtime.resolve(request.id, "reject")
|
|
933
|
+
return
|
|
934
|
+
|
|
935
|
+
if result.request_id != request.id:
|
|
936
|
+
logger.warning(
|
|
937
|
+
"Approval response id mismatch: request={request_id}, "
|
|
938
|
+
"response={response_id}",
|
|
939
|
+
request_id=request.id,
|
|
940
|
+
response_id=result.request_id,
|
|
941
|
+
)
|
|
942
|
+
request.resolve(result.response)
|
|
943
|
+
if self._approval_runtime is not None:
|
|
944
|
+
self._approval_runtime.resolve(
|
|
945
|
+
request.id, result.response, feedback=result.feedback
|
|
946
|
+
)
|
|
947
|
+
case ToolCallRequest():
|
|
948
|
+
if isinstance(msg, JSONRPCErrorResponse):
|
|
949
|
+
error = msg.error.message
|
|
950
|
+
request.resolve(
|
|
951
|
+
ToolError(
|
|
952
|
+
message=error,
|
|
953
|
+
brief="External tool error",
|
|
954
|
+
)
|
|
955
|
+
)
|
|
956
|
+
return
|
|
957
|
+
|
|
958
|
+
try:
|
|
959
|
+
tool_result = ToolResult.model_validate(msg.result)
|
|
960
|
+
except pydantic.ValidationError as e:
|
|
961
|
+
logger.error(
|
|
962
|
+
"Invalid tool result for request id={id}: {error}",
|
|
963
|
+
id=msg.id,
|
|
964
|
+
error=e,
|
|
965
|
+
)
|
|
966
|
+
request.resolve(
|
|
967
|
+
ToolError(
|
|
968
|
+
message="Invalid tool result payload from client.",
|
|
969
|
+
brief="Invalid tool result",
|
|
970
|
+
)
|
|
971
|
+
)
|
|
972
|
+
return
|
|
973
|
+
if tool_result.tool_call_id != request.id:
|
|
974
|
+
logger.warning(
|
|
975
|
+
"Tool result id mismatch: request={request_id}, result={result_id}",
|
|
976
|
+
request_id=request.id,
|
|
977
|
+
result_id=tool_result.tool_call_id,
|
|
978
|
+
)
|
|
979
|
+
request.resolve(tool_result.return_value)
|
|
980
|
+
case QuestionRequest():
|
|
981
|
+
if isinstance(msg, JSONRPCErrorResponse):
|
|
982
|
+
request.resolve({})
|
|
983
|
+
return
|
|
984
|
+
|
|
985
|
+
try:
|
|
986
|
+
result = QuestionResponse.model_validate(msg.result)
|
|
987
|
+
except pydantic.ValidationError as e:
|
|
988
|
+
logger.error(
|
|
989
|
+
"Invalid question response for request id={id}: {error}",
|
|
990
|
+
id=msg.id,
|
|
991
|
+
error=e,
|
|
992
|
+
)
|
|
993
|
+
request.resolve({})
|
|
994
|
+
return
|
|
995
|
+
|
|
996
|
+
if result.request_id != request.id:
|
|
997
|
+
logger.warning(
|
|
998
|
+
"Question response id mismatch: request={request_id}, "
|
|
999
|
+
"response={response_id}",
|
|
1000
|
+
request_id=request.id,
|
|
1001
|
+
response_id=result.request_id,
|
|
1002
|
+
)
|
|
1003
|
+
request.resolve(result.answers)
|
|
1004
|
+
case HookRequest():
|
|
1005
|
+
if isinstance(msg, JSONRPCErrorResponse):
|
|
1006
|
+
request.resolve("allow")
|
|
1007
|
+
return
|
|
1008
|
+
|
|
1009
|
+
try:
|
|
1010
|
+
result = HookResponse.model_validate(msg.result)
|
|
1011
|
+
except pydantic.ValidationError as e:
|
|
1012
|
+
logger.error(
|
|
1013
|
+
"Invalid hook response for request id={id}: {error}",
|
|
1014
|
+
id=msg.id,
|
|
1015
|
+
error=e,
|
|
1016
|
+
)
|
|
1017
|
+
request.resolve("allow")
|
|
1018
|
+
return
|
|
1019
|
+
|
|
1020
|
+
if result.request_id != request.id:
|
|
1021
|
+
logger.warning(
|
|
1022
|
+
"Hook response id mismatch: request={request_id}, response={response_id}",
|
|
1023
|
+
request_id=request.id,
|
|
1024
|
+
response_id=result.request_id,
|
|
1025
|
+
)
|
|
1026
|
+
request.resolve(result.action, result.reason)
|
|
1027
|
+
|
|
1028
|
+
async def _stream_wire_messages(self, wire: Wire) -> None:
|
|
1029
|
+
wire_ui = wire.ui_side(merge=False)
|
|
1030
|
+
while True:
|
|
1031
|
+
msg = await wire_ui.receive()
|
|
1032
|
+
match msg:
|
|
1033
|
+
case ApprovalRequest():
|
|
1034
|
+
await self._request_approval(msg)
|
|
1035
|
+
case ToolCallRequest():
|
|
1036
|
+
await self._request_external_tool(msg)
|
|
1037
|
+
case QuestionRequest():
|
|
1038
|
+
await self._request_question(msg)
|
|
1039
|
+
case HookRequest():
|
|
1040
|
+
pass # handled via hook engine callbacks
|
|
1041
|
+
case _:
|
|
1042
|
+
await self._send_msg(JSONRPCEventMessage(method="event", params=msg))
|
|
1043
|
+
|
|
1044
|
+
async def _request_approval(self, request: ApprovalRequest) -> None:
|
|
1045
|
+
msg_id = request.id # just use the approval request id as message id
|
|
1046
|
+
self._pending_requests[msg_id] = request
|
|
1047
|
+
await self._send_msg(JSONRPCRequestMessage(id=msg_id, params=request))
|
|
1048
|
+
# Do NOT await request.wait() here. The approval future is awaited by
|
|
1049
|
+
# the tool that created the request (inside the soul task). Blocking the
|
|
1050
|
+
# UI loop would prevent ALL subsequent Wire messages — from every
|
|
1051
|
+
# concurrent subagent — from reaching stdout, causing a cascade deadlock
|
|
1052
|
+
# when the approval response is lost (e.g. no WebSocket connected).
|
|
1053
|
+
|
|
1054
|
+
async def _request_external_tool(self, request: ToolCallRequest) -> None:
|
|
1055
|
+
msg_id = request.id
|
|
1056
|
+
self._pending_requests[msg_id] = request
|
|
1057
|
+
await self._send_msg(JSONRPCRequestMessage(id=msg_id, params=request))
|
|
1058
|
+
# Same rationale as _request_approval: do not block the UI loop.
|
|
1059
|
+
|
|
1060
|
+
async def _request_question(self, request: QuestionRequest) -> None:
|
|
1061
|
+
if not self._client_supports_question:
|
|
1062
|
+
# Client does not support interactive questions; signal the tool
|
|
1063
|
+
# so it can tell the LLM to use an alternative approach.
|
|
1064
|
+
request.set_exception(QuestionNotSupported())
|
|
1065
|
+
return
|
|
1066
|
+
msg_id = request.id
|
|
1067
|
+
self._pending_requests[msg_id] = request
|
|
1068
|
+
await self._send_msg(JSONRPCRequestMessage(id=msg_id, params=request))
|
|
1069
|
+
# Same rationale as _request_approval: do not block the UI loop.
|