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,267 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
from pythinker_code.approval_runtime import (
|
|
8
|
+
ApprovalCancelledError,
|
|
9
|
+
ApprovalRuntime,
|
|
10
|
+
ApprovalSource,
|
|
11
|
+
get_current_approval_source_or_none,
|
|
12
|
+
)
|
|
13
|
+
from pythinker_code.soul.toolset import get_current_tool_call_or_none
|
|
14
|
+
from pythinker_code.tools.utils import ToolRejectedError
|
|
15
|
+
from pythinker_code.utils.logging import logger
|
|
16
|
+
from pythinker_code.wire.types import DisplayBlock
|
|
17
|
+
|
|
18
|
+
type Response = Literal["approve", "approve_for_session", "reject"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ApprovalResult:
|
|
22
|
+
"""Result of an approval request. Behaves as bool for backward compatibility."""
|
|
23
|
+
|
|
24
|
+
__slots__ = ("approved", "feedback")
|
|
25
|
+
|
|
26
|
+
def __init__(self, approved: bool, feedback: str = ""):
|
|
27
|
+
self.approved = approved
|
|
28
|
+
self.feedback = feedback
|
|
29
|
+
|
|
30
|
+
def __bool__(self) -> bool:
|
|
31
|
+
return self.approved
|
|
32
|
+
|
|
33
|
+
def rejection_error(self) -> ToolRejectedError:
|
|
34
|
+
if self.feedback:
|
|
35
|
+
return ToolRejectedError(
|
|
36
|
+
message=(f"The tool call is rejected by the user. User feedback: {self.feedback}"),
|
|
37
|
+
brief=f"Rejected: {self.feedback}",
|
|
38
|
+
has_feedback=True,
|
|
39
|
+
)
|
|
40
|
+
source = get_current_approval_source_or_none()
|
|
41
|
+
is_subagent = source is not None and source.agent_id is not None
|
|
42
|
+
if is_subagent:
|
|
43
|
+
return ToolRejectedError(
|
|
44
|
+
message=(
|
|
45
|
+
"The tool call is rejected by the user. "
|
|
46
|
+
"Try a different approach to complete your task, or explain the "
|
|
47
|
+
"limitation in your summary if no alternative is available. "
|
|
48
|
+
"Do not retry the same tool call, and do not attempt to bypass "
|
|
49
|
+
"this restriction through indirect means."
|
|
50
|
+
),
|
|
51
|
+
)
|
|
52
|
+
return ToolRejectedError()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ApprovalState:
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
yolo: bool = False,
|
|
59
|
+
auto: bool = False,
|
|
60
|
+
runtime_auto: bool = False,
|
|
61
|
+
auto_approve_actions: set[str] | None = None,
|
|
62
|
+
on_change: Callable[[], None] | None = None,
|
|
63
|
+
):
|
|
64
|
+
self.yolo = yolo
|
|
65
|
+
self.auto = auto
|
|
66
|
+
"""Persisted session flag. True when no user is present (auto mode).
|
|
67
|
+
|
|
68
|
+
Implies auto-approve and is restored with the session.
|
|
69
|
+
"""
|
|
70
|
+
self.runtime_auto = runtime_auto
|
|
71
|
+
"""Invocation-only auto flag, e.g. ``--auto`` or ``--print``. Not persisted."""
|
|
72
|
+
self.auto_approve_actions: set[str] = auto_approve_actions or set()
|
|
73
|
+
"""Set of action names that should automatically be approved."""
|
|
74
|
+
self._on_change = on_change
|
|
75
|
+
|
|
76
|
+
def notify_change(self) -> None:
|
|
77
|
+
if self._on_change is not None:
|
|
78
|
+
self._on_change()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class Approval:
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
yolo: bool = False,
|
|
85
|
+
*,
|
|
86
|
+
state: ApprovalState | None = None,
|
|
87
|
+
runtime: ApprovalRuntime | None = None,
|
|
88
|
+
):
|
|
89
|
+
self._state = state or ApprovalState(yolo=yolo)
|
|
90
|
+
self._runtime = runtime or ApprovalRuntime()
|
|
91
|
+
|
|
92
|
+
def share(self) -> Approval:
|
|
93
|
+
"""Create a new approval queue that shares approval state."""
|
|
94
|
+
return Approval(state=self._state, runtime=self._runtime)
|
|
95
|
+
|
|
96
|
+
def set_runtime(self, runtime: ApprovalRuntime) -> None:
|
|
97
|
+
self._runtime = runtime
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def runtime(self) -> ApprovalRuntime:
|
|
101
|
+
return self._runtime
|
|
102
|
+
|
|
103
|
+
def set_yolo(self, yolo: bool) -> None:
|
|
104
|
+
self._state.yolo = yolo
|
|
105
|
+
self._state.notify_change()
|
|
106
|
+
|
|
107
|
+
def set_auto(self, auto: bool) -> None:
|
|
108
|
+
"""Toggle persisted auto (unattended, no user present) mode.
|
|
109
|
+
|
|
110
|
+
Turning it off also clears any invocation-only auto overlay so an
|
|
111
|
+
interactive session started with ``--auto`` can return to interactive
|
|
112
|
+
behavior via ``/auto``.
|
|
113
|
+
"""
|
|
114
|
+
self._state.auto = auto
|
|
115
|
+
if not auto:
|
|
116
|
+
self._state.runtime_auto = False
|
|
117
|
+
self._state.notify_change()
|
|
118
|
+
|
|
119
|
+
def set_runtime_auto(self, auto: bool) -> None:
|
|
120
|
+
"""Toggle invocation-only auto mode without persisting it."""
|
|
121
|
+
self._state.runtime_auto = auto
|
|
122
|
+
|
|
123
|
+
def is_auto_approve(self) -> bool:
|
|
124
|
+
"""True when tool calls should be auto-approved.
|
|
125
|
+
|
|
126
|
+
Auto mode implies auto-approve, so this returns True whenever either the
|
|
127
|
+
explicit yolo flag or auto mode is set.
|
|
128
|
+
"""
|
|
129
|
+
return self._state.yolo or self.is_auto()
|
|
130
|
+
|
|
131
|
+
def is_yolo(self) -> bool:
|
|
132
|
+
"""True only when the user explicitly opted into yolo."""
|
|
133
|
+
return self._state.yolo
|
|
134
|
+
|
|
135
|
+
def is_yolo_flag(self) -> bool:
|
|
136
|
+
"""True only when the user explicitly opted into yolo (not via auto)."""
|
|
137
|
+
return self.is_yolo()
|
|
138
|
+
|
|
139
|
+
def is_auto(self) -> bool:
|
|
140
|
+
"""True when no user is present (auto mode)."""
|
|
141
|
+
return self._state.auto or self._state.runtime_auto
|
|
142
|
+
|
|
143
|
+
def is_auto_flag(self) -> bool:
|
|
144
|
+
"""True only when persisted auto mode is active."""
|
|
145
|
+
return self._state.auto
|
|
146
|
+
|
|
147
|
+
def is_runtime_auto(self) -> bool:
|
|
148
|
+
"""True only when auto mode came from this invocation."""
|
|
149
|
+
return self._state.runtime_auto
|
|
150
|
+
|
|
151
|
+
async def request(
|
|
152
|
+
self,
|
|
153
|
+
sender: str,
|
|
154
|
+
action: str,
|
|
155
|
+
description: str,
|
|
156
|
+
display: list[DisplayBlock] | None = None,
|
|
157
|
+
) -> ApprovalResult:
|
|
158
|
+
"""
|
|
159
|
+
Request approval for the given action. Intended to be called by tools.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
sender (str): The name of the sender.
|
|
163
|
+
action (str): The action to request approval for.
|
|
164
|
+
This is used to identify the action for auto-approval.
|
|
165
|
+
description (str): The description of the action. This is used to display to the user.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
ApprovalResult: Result with ``approved`` flag and optional ``feedback``.
|
|
169
|
+
Behaves as ``bool`` via ``__bool__``, so ``if not result:`` works.
|
|
170
|
+
|
|
171
|
+
Raises:
|
|
172
|
+
RuntimeError: If the approval is requested from outside a tool call.
|
|
173
|
+
"""
|
|
174
|
+
tool_call = get_current_tool_call_or_none()
|
|
175
|
+
if tool_call is None:
|
|
176
|
+
raise RuntimeError("Approval must be requested from a tool call.")
|
|
177
|
+
|
|
178
|
+
logger.debug(
|
|
179
|
+
"{tool_name} ({tool_call_id}) requesting approval: {action} {description}",
|
|
180
|
+
tool_name=tool_call.function.name,
|
|
181
|
+
tool_call_id=tool_call.id,
|
|
182
|
+
action=action,
|
|
183
|
+
description=description,
|
|
184
|
+
)
|
|
185
|
+
if self.is_auto_approve():
|
|
186
|
+
from pythinker_code.telemetry import track
|
|
187
|
+
|
|
188
|
+
track(
|
|
189
|
+
"tool_approved",
|
|
190
|
+
tool_name=tool_call.function.name,
|
|
191
|
+
approval_mode="auto" if self.is_auto() else "yolo",
|
|
192
|
+
)
|
|
193
|
+
return ApprovalResult(approved=True)
|
|
194
|
+
|
|
195
|
+
if action in self._state.auto_approve_actions:
|
|
196
|
+
from pythinker_code.telemetry import track
|
|
197
|
+
|
|
198
|
+
track(
|
|
199
|
+
"tool_approved",
|
|
200
|
+
tool_name=tool_call.function.name,
|
|
201
|
+
approval_mode="auto_session",
|
|
202
|
+
)
|
|
203
|
+
return ApprovalResult(approved=True)
|
|
204
|
+
|
|
205
|
+
request_id = str(uuid.uuid4())
|
|
206
|
+
display_blocks = display or []
|
|
207
|
+
source = get_current_approval_source_or_none() or ApprovalSource(
|
|
208
|
+
kind="foreground_turn",
|
|
209
|
+
id=tool_call.id,
|
|
210
|
+
)
|
|
211
|
+
self._runtime.create_request(
|
|
212
|
+
request_id=request_id,
|
|
213
|
+
tool_call_id=tool_call.id,
|
|
214
|
+
sender=sender,
|
|
215
|
+
action=action,
|
|
216
|
+
description=description,
|
|
217
|
+
display=display_blocks,
|
|
218
|
+
source=source,
|
|
219
|
+
)
|
|
220
|
+
try:
|
|
221
|
+
response, feedback = await self._runtime.wait_for_response(request_id)
|
|
222
|
+
except ApprovalCancelledError:
|
|
223
|
+
from pythinker_code.telemetry import track
|
|
224
|
+
|
|
225
|
+
track(
|
|
226
|
+
"tool_rejected",
|
|
227
|
+
tool_name=tool_call.function.name,
|
|
228
|
+
approval_mode="cancelled",
|
|
229
|
+
)
|
|
230
|
+
record = self._runtime.get_request(request_id)
|
|
231
|
+
return ApprovalResult(approved=False, feedback=record.feedback if record else "")
|
|
232
|
+
from pythinker_code.telemetry import track
|
|
233
|
+
|
|
234
|
+
match response:
|
|
235
|
+
case "approve":
|
|
236
|
+
track(
|
|
237
|
+
"tool_approved",
|
|
238
|
+
tool_name=tool_call.function.name,
|
|
239
|
+
approval_mode="manual",
|
|
240
|
+
)
|
|
241
|
+
return ApprovalResult(approved=True)
|
|
242
|
+
case "approve_for_session":
|
|
243
|
+
track(
|
|
244
|
+
"tool_approved",
|
|
245
|
+
tool_name=tool_call.function.name,
|
|
246
|
+
approval_mode="manual",
|
|
247
|
+
)
|
|
248
|
+
self._state.auto_approve_actions.add(action)
|
|
249
|
+
self._state.notify_change()
|
|
250
|
+
for pending in self._runtime.list_pending():
|
|
251
|
+
if pending.action == action:
|
|
252
|
+
self._runtime.resolve(pending.id, "approve")
|
|
253
|
+
return ApprovalResult(approved=True)
|
|
254
|
+
case "reject":
|
|
255
|
+
track(
|
|
256
|
+
"tool_rejected",
|
|
257
|
+
tool_name=tool_call.function.name,
|
|
258
|
+
approval_mode="manual",
|
|
259
|
+
)
|
|
260
|
+
return ApprovalResult(approved=False, feedback=feedback)
|
|
261
|
+
case _:
|
|
262
|
+
track(
|
|
263
|
+
"tool_rejected",
|
|
264
|
+
tool_name=tool_call.function.name,
|
|
265
|
+
approval_mode="manual",
|
|
266
|
+
)
|
|
267
|
+
return ApprovalResult(approved=False)
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Side question ("/btw") - answer a quick question without interrupting the main conversation.
|
|
2
|
+
|
|
3
|
+
Uses the same system_prompt + normalized history + tool definitions as the main
|
|
4
|
+
agent to maximize prompt cache hits. Tools are declared (for cache) but denied
|
|
5
|
+
at execution time. maxTurns=2 so if the LLM mistakenly calls a tool on the
|
|
6
|
+
first turn, the error result gives it a second chance to answer with text.
|
|
7
|
+
|
|
8
|
+
The question and response are NOT written to the main context history.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import uuid
|
|
14
|
+
from collections.abc import Callable
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
import pythinker_core
|
|
18
|
+
from pythinker_core.message import Message, ToolCall
|
|
19
|
+
from pythinker_core.tooling import Tool, ToolError, ToolResult
|
|
20
|
+
|
|
21
|
+
from pythinker_code.soul import LLMNotSet, wire_send
|
|
22
|
+
from pythinker_code.soul.dynamic_injection import normalize_history
|
|
23
|
+
from pythinker_code.soul.message import system_reminder
|
|
24
|
+
from pythinker_code.utils.logging import logger
|
|
25
|
+
from pythinker_code.wire.types import BtwBegin, BtwEnd, TextPart
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from pythinker_core.chat_provider import StreamedMessagePart
|
|
29
|
+
|
|
30
|
+
from pythinker_code.soul.pythinkersoul import PythinkerSoul
|
|
31
|
+
|
|
32
|
+
_BTW_MAX_TURNS = 2
|
|
33
|
+
|
|
34
|
+
SIDE_QUESTION_SYSTEM_REMINDER = """\
|
|
35
|
+
This is a side question from the user. Answer directly in a single response.
|
|
36
|
+
|
|
37
|
+
IMPORTANT:
|
|
38
|
+
- You are a separate, lightweight instance answering one question.
|
|
39
|
+
- The main agent continues independently — do NOT reference being interrupted.
|
|
40
|
+
- Do NOT call any tools. All tool calls are disabled and will be rejected.
|
|
41
|
+
Even though tool definitions are visible in this request, they exist only
|
|
42
|
+
for technical reasons (prompt cache). You MUST NOT use them.
|
|
43
|
+
- Respond ONLY with text based on what you already know from the conversation.
|
|
44
|
+
- This is a one-off response — no follow-up turns.
|
|
45
|
+
- If you don't know the answer, say so directly."""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# DenyAllToolset: advertises tools (cache match) but rejects every call
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class _DenyAllToolset:
|
|
54
|
+
"""A toolset that exposes the same tool definitions as the agent (for prompt
|
|
55
|
+
cache matching) but rejects every tool call with an error message."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, source_tools: list[Tool]) -> None:
|
|
58
|
+
self._tools = source_tools
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def tools(self) -> list[Tool]:
|
|
62
|
+
return self._tools
|
|
63
|
+
|
|
64
|
+
def handle(self, tool_call: ToolCall) -> ToolResult:
|
|
65
|
+
return ToolResult(
|
|
66
|
+
tool_call_id=tool_call.id,
|
|
67
|
+
return_value=ToolError(
|
|
68
|
+
message="Tool calls are disabled for side questions. Answer with text only.",
|
|
69
|
+
brief="denied",
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
# Context construction
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _build_btw_context(
|
|
80
|
+
soul: PythinkerSoul, question: str
|
|
81
|
+
) -> tuple[str, list[Message], _DenyAllToolset]:
|
|
82
|
+
"""Build (system_prompt, history, toolset) aligned with the main agent.
|
|
83
|
+
|
|
84
|
+
Uses the same system_prompt, normalize_history(), and tool definitions
|
|
85
|
+
as ``PythinkerSoul._step`` so the LLM provider can reuse the prompt cache.
|
|
86
|
+
"""
|
|
87
|
+
system_prompt = soul._agent.system_prompt # pyright: ignore[reportPrivateUsage]
|
|
88
|
+
effective_history = normalize_history(soul.context.history)
|
|
89
|
+
|
|
90
|
+
wrapped = f"{system_reminder(SIDE_QUESTION_SYSTEM_REMINDER).text}\n\n{question}"
|
|
91
|
+
side_message = Message(role="user", content=wrapped)
|
|
92
|
+
|
|
93
|
+
toolset = _DenyAllToolset(soul._agent.toolset.tools) # pyright: ignore[reportPrivateUsage]
|
|
94
|
+
|
|
95
|
+
return system_prompt, [*effective_history, side_message], toolset
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
# Execution
|
|
100
|
+
# ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
async def execute_side_question(
|
|
104
|
+
soul: PythinkerSoul,
|
|
105
|
+
question: str,
|
|
106
|
+
on_text_chunk: Callable[[str], None] | None = None,
|
|
107
|
+
) -> tuple[str | None, str | None]:
|
|
108
|
+
"""Execute a side question and return (response, error).
|
|
109
|
+
|
|
110
|
+
Runs up to ``_BTW_MAX_TURNS`` steps. On the first step, if the LLM
|
|
111
|
+
returns a tool call instead of text, the denied tool result is appended
|
|
112
|
+
to the history and a second step gives the LLM another chance.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
soul: The PythinkerSoul instance (for context and chat_provider access).
|
|
116
|
+
question: The user's side question.
|
|
117
|
+
on_text_chunk: Optional callback for streaming text chunks.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
(response_text, None) on success, (None, error_message) on failure.
|
|
121
|
+
"""
|
|
122
|
+
if soul._runtime.llm is None: # pyright: ignore[reportPrivateUsage]
|
|
123
|
+
return None, "LLM is not set."
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
chat_provider = soul._runtime.llm.chat_provider # pyright: ignore[reportPrivateUsage]
|
|
127
|
+
system_prompt, history, toolset = _build_btw_context(soul, question)
|
|
128
|
+
|
|
129
|
+
text_chunks: list[str] = []
|
|
130
|
+
|
|
131
|
+
def _on_part(part: StreamedMessagePart) -> None:
|
|
132
|
+
if isinstance(part, TextPart) and part.text:
|
|
133
|
+
text_chunks.append(part.text)
|
|
134
|
+
if on_text_chunk is not None:
|
|
135
|
+
on_text_chunk(part.text)
|
|
136
|
+
|
|
137
|
+
# Multi-turn loop: give the LLM a second chance if it calls tools
|
|
138
|
+
for turn in range(_BTW_MAX_TURNS):
|
|
139
|
+
result = await pythinker_core.step(
|
|
140
|
+
chat_provider,
|
|
141
|
+
system_prompt,
|
|
142
|
+
toolset,
|
|
143
|
+
history,
|
|
144
|
+
on_message_part=_on_part,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Check for text response — but only accept it if the LLM
|
|
148
|
+
# didn't also call tools (mixed text+tool = incomplete preamble).
|
|
149
|
+
response_text = "".join(text_chunks).strip()
|
|
150
|
+
if response_text and not result.tool_calls:
|
|
151
|
+
return response_text, None
|
|
152
|
+
|
|
153
|
+
# No text — did the LLM try to call a tool?
|
|
154
|
+
tool_results = await result.tool_results()
|
|
155
|
+
if not result.tool_calls:
|
|
156
|
+
break # No text, no tool calls — give up
|
|
157
|
+
|
|
158
|
+
# Tool calls were denied. If we have turns left, feed the error
|
|
159
|
+
# back so the LLM can try again with text.
|
|
160
|
+
if turn + 1 < _BTW_MAX_TURNS:
|
|
161
|
+
# Build the next history: original + assistant message + tool error results
|
|
162
|
+
history = [
|
|
163
|
+
*history,
|
|
164
|
+
result.message,
|
|
165
|
+
*[_tool_result_to_message(tr) for tr in tool_results],
|
|
166
|
+
]
|
|
167
|
+
text_chunks.clear() # Reset for next turn
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
# Last turn and still no text — report the tool call attempt
|
|
171
|
+
tool_names = [tc.function.name for tc in result.tool_calls]
|
|
172
|
+
return None, (
|
|
173
|
+
f"Side question tried to call tools ({', '.join(tool_names)}) "
|
|
174
|
+
"instead of answering directly. Try rephrasing or ask in the main conversation."
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return None, "No response received."
|
|
178
|
+
except Exception as e:
|
|
179
|
+
logger.warning("Side question failed: {error}", error=e)
|
|
180
|
+
return None, str(e)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _tool_result_to_message(tool_result: ToolResult) -> Message:
|
|
184
|
+
"""Convert a ToolResult to a tool-result Message for history."""
|
|
185
|
+
content = tool_result.return_value.message or "Tool call denied."
|
|
186
|
+
return Message(
|
|
187
|
+
role="tool",
|
|
188
|
+
content=content,
|
|
189
|
+
tool_call_id=tool_result.tool_call_id,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# ---------------------------------------------------------------------------
|
|
194
|
+
# Wire-based entry point (for web UI / non-interactive)
|
|
195
|
+
# ---------------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
async def run_side_question(soul: PythinkerSoul, question: str) -> None:
|
|
199
|
+
"""Execute a side question via wire events."""
|
|
200
|
+
if soul._runtime.llm is None: # pyright: ignore[reportPrivateUsage]
|
|
201
|
+
raise LLMNotSet()
|
|
202
|
+
|
|
203
|
+
btw_id = uuid.uuid4().hex[:12]
|
|
204
|
+
wire_send(BtwBegin(id=btw_id, question=question))
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
response, error = await execute_side_question(soul, question)
|
|
208
|
+
if response:
|
|
209
|
+
wire_send(BtwEnd(id=btw_id, response=response))
|
|
210
|
+
else:
|
|
211
|
+
wire_send(BtwEnd(id=btw_id, error=error or "No response received."))
|
|
212
|
+
except Exception as e:
|
|
213
|
+
logger.warning("Side question failed: {error}", error=e)
|
|
214
|
+
wire_send(BtwEnd(id=btw_id, error=str(e)))
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from typing import TYPE_CHECKING, NamedTuple, Protocol, runtime_checkable
|
|
5
|
+
|
|
6
|
+
import pythinker_core
|
|
7
|
+
from pythinker_core.chat_provider import TokenUsage
|
|
8
|
+
from pythinker_core.message import Message
|
|
9
|
+
from pythinker_core.tooling.empty import EmptyToolset
|
|
10
|
+
|
|
11
|
+
import pythinker_code.prompts as prompts
|
|
12
|
+
from pythinker_code.llm import LLM
|
|
13
|
+
from pythinker_code.soul.message import system
|
|
14
|
+
from pythinker_code.utils.logging import logger
|
|
15
|
+
from pythinker_code.wire.types import ContentPart, TextPart, ThinkPart
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CompactionResult(NamedTuple):
|
|
19
|
+
messages: Sequence[Message]
|
|
20
|
+
usage: TokenUsage | None
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def estimated_token_count(self) -> int:
|
|
24
|
+
"""Estimate the token count of the compacted messages.
|
|
25
|
+
|
|
26
|
+
When LLM usage is available, ``usage.output`` gives the exact token count
|
|
27
|
+
of the generated summary (the first message). Preserved messages (all
|
|
28
|
+
subsequent messages) are estimated from their text length.
|
|
29
|
+
|
|
30
|
+
When usage is not available (no compaction LLM call was made), all
|
|
31
|
+
messages are estimated from text length.
|
|
32
|
+
|
|
33
|
+
The estimate is intentionally conservative — it will be replaced by the
|
|
34
|
+
real value on the next LLM call.
|
|
35
|
+
"""
|
|
36
|
+
if self.usage is not None and len(self.messages) > 0:
|
|
37
|
+
summary_tokens = self.usage.output
|
|
38
|
+
preserved_tokens = estimate_text_tokens(self.messages[1:])
|
|
39
|
+
return summary_tokens + preserved_tokens
|
|
40
|
+
|
|
41
|
+
return estimate_text_tokens(self.messages)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def estimate_text_tokens(messages: Sequence[Message]) -> int:
|
|
45
|
+
"""Estimate tokens from message text content using a character-based heuristic."""
|
|
46
|
+
total_chars = 0
|
|
47
|
+
for msg in messages:
|
|
48
|
+
for part in msg.content:
|
|
49
|
+
if isinstance(part, TextPart):
|
|
50
|
+
total_chars += len(part.text)
|
|
51
|
+
# ~4 chars per token for English; somewhat underestimates for CJK text,
|
|
52
|
+
# but this is a temporary estimate that gets corrected on the next LLM call.
|
|
53
|
+
return total_chars // 4
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def should_auto_compact(
|
|
57
|
+
token_count: int,
|
|
58
|
+
max_context_size: int,
|
|
59
|
+
*,
|
|
60
|
+
trigger_ratio: float,
|
|
61
|
+
reserved_context_size: int,
|
|
62
|
+
) -> bool:
|
|
63
|
+
"""Determine whether auto-compaction should be triggered.
|
|
64
|
+
|
|
65
|
+
Returns True when either condition is met (whichever fires first):
|
|
66
|
+
- Ratio-based: token_count >= max_context_size * trigger_ratio
|
|
67
|
+
- Reserved-based: token_count + reserved_context_size >= max_context_size
|
|
68
|
+
"""
|
|
69
|
+
return (
|
|
70
|
+
token_count >= max_context_size * trigger_ratio
|
|
71
|
+
or token_count + reserved_context_size >= max_context_size
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@runtime_checkable
|
|
76
|
+
class Compaction(Protocol):
|
|
77
|
+
async def compact(
|
|
78
|
+
self, messages: Sequence[Message], llm: LLM, *, custom_instruction: str = ""
|
|
79
|
+
) -> CompactionResult:
|
|
80
|
+
"""
|
|
81
|
+
Compact a sequence of messages into a new sequence of messages.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
messages (Sequence[Message]): The messages to compact.
|
|
85
|
+
llm (LLM): The LLM to use for compaction.
|
|
86
|
+
custom_instruction: Optional user instruction to guide compaction focus.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
CompactionResult: The compacted messages and token usage from the compaction LLM call.
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
ChatProviderError: When the chat provider returns an error.
|
|
93
|
+
"""
|
|
94
|
+
...
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if TYPE_CHECKING:
|
|
98
|
+
|
|
99
|
+
def type_check(simple: SimpleCompaction):
|
|
100
|
+
_: Compaction = simple
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class SimpleCompaction:
|
|
104
|
+
def __init__(self, max_preserved_messages: int = 2) -> None:
|
|
105
|
+
self.max_preserved_messages = max_preserved_messages
|
|
106
|
+
|
|
107
|
+
async def compact(
|
|
108
|
+
self, messages: Sequence[Message], llm: LLM, *, custom_instruction: str = ""
|
|
109
|
+
) -> CompactionResult:
|
|
110
|
+
compact_message, to_preserve = self.prepare(messages, custom_instruction=custom_instruction)
|
|
111
|
+
if compact_message is None:
|
|
112
|
+
return CompactionResult(messages=to_preserve, usage=None)
|
|
113
|
+
|
|
114
|
+
# Call pythinker_core.step to get the compacted context
|
|
115
|
+
# TODO: set max completion tokens
|
|
116
|
+
logger.debug("Compacting context...")
|
|
117
|
+
result = await pythinker_core.step(
|
|
118
|
+
chat_provider=llm.chat_provider,
|
|
119
|
+
system_prompt="You are a helpful assistant that compacts conversation context.",
|
|
120
|
+
toolset=EmptyToolset(),
|
|
121
|
+
history=[compact_message],
|
|
122
|
+
)
|
|
123
|
+
if result.usage:
|
|
124
|
+
logger.debug(
|
|
125
|
+
"Compaction used {input} input tokens and {output} output tokens",
|
|
126
|
+
input=result.usage.input,
|
|
127
|
+
output=result.usage.output,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
content: list[ContentPart] = [
|
|
131
|
+
system("Previous context has been compacted. Here is the compaction output:")
|
|
132
|
+
]
|
|
133
|
+
compacted_msg = result.message
|
|
134
|
+
|
|
135
|
+
# drop thinking parts if any
|
|
136
|
+
content.extend(part for part in compacted_msg.content if not isinstance(part, ThinkPart))
|
|
137
|
+
compacted_messages: list[Message] = [Message(role="user", content=content)]
|
|
138
|
+
compacted_messages.extend(to_preserve)
|
|
139
|
+
return CompactionResult(messages=compacted_messages, usage=result.usage)
|
|
140
|
+
|
|
141
|
+
class PrepareResult(NamedTuple):
|
|
142
|
+
compact_message: Message | None
|
|
143
|
+
to_preserve: Sequence[Message]
|
|
144
|
+
|
|
145
|
+
def prepare(
|
|
146
|
+
self, messages: Sequence[Message], *, custom_instruction: str = ""
|
|
147
|
+
) -> PrepareResult:
|
|
148
|
+
if not messages or self.max_preserved_messages <= 0:
|
|
149
|
+
return self.PrepareResult(compact_message=None, to_preserve=messages)
|
|
150
|
+
|
|
151
|
+
history = list(messages)
|
|
152
|
+
preserve_start_index = len(history)
|
|
153
|
+
n_preserved = 0
|
|
154
|
+
for index in range(len(history) - 1, -1, -1):
|
|
155
|
+
if history[index].role in {"user", "assistant"}:
|
|
156
|
+
n_preserved += 1
|
|
157
|
+
if n_preserved == self.max_preserved_messages:
|
|
158
|
+
preserve_start_index = index
|
|
159
|
+
break
|
|
160
|
+
|
|
161
|
+
if n_preserved < self.max_preserved_messages:
|
|
162
|
+
return self.PrepareResult(compact_message=None, to_preserve=messages)
|
|
163
|
+
|
|
164
|
+
to_compact = history[:preserve_start_index]
|
|
165
|
+
to_preserve = history[preserve_start_index:]
|
|
166
|
+
|
|
167
|
+
if not to_compact:
|
|
168
|
+
# Let's hope this won't exceed the context size limit
|
|
169
|
+
return self.PrepareResult(compact_message=None, to_preserve=to_preserve)
|
|
170
|
+
|
|
171
|
+
# Create input message for compaction
|
|
172
|
+
compact_message = Message(role="user", content=[])
|
|
173
|
+
for i, msg in enumerate(to_compact):
|
|
174
|
+
compact_message.content.append(
|
|
175
|
+
TextPart(text=f"## Message {i + 1}\nRole: {msg.role}\nContent:\n")
|
|
176
|
+
)
|
|
177
|
+
compact_message.content.extend(
|
|
178
|
+
part for part in msg.content if isinstance(part, TextPart)
|
|
179
|
+
)
|
|
180
|
+
prompt_text = "\n" + prompts.COMPACT
|
|
181
|
+
if custom_instruction:
|
|
182
|
+
prompt_text += (
|
|
183
|
+
"\n\n**User's Custom Compaction Instruction:**\n"
|
|
184
|
+
"The user has specifically requested the following focus during compaction. "
|
|
185
|
+
"You MUST prioritize this instruction above the default compression priorities:\n"
|
|
186
|
+
f"{custom_instruction}"
|
|
187
|
+
)
|
|
188
|
+
compact_message.content.append(TextPart(text=prompt_text))
|
|
189
|
+
return self.PrepareResult(compact_message=compact_message, to_preserve=to_preserve)
|