pythinker-code 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythinker_code/CHANGELOG.md +60 -0
- pythinker_code/__init__.py +0 -0
- pythinker_code/__main__.py +97 -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 +301 -0
- pythinker_code/acp/mcp.py +46 -0
- pythinker_code/acp/server.py +497 -0
- pythinker_code/acp/session.py +502 -0
- pythinker_code/acp/tools.py +174 -0
- pythinker_code/acp/types.py +13 -0
- pythinker_code/acp/version.py +45 -0
- pythinker_code/agents/default/agent.yaml +55 -0
- pythinker_code/agents/default/code_reviewer.yaml +47 -0
- pythinker_code/agents/default/coder.yaml +42 -0
- pythinker_code/agents/default/debugger.yaml +35 -0
- pythinker_code/agents/default/explore.yaml +59 -0
- pythinker_code/agents/default/implementer.yaml +46 -0
- pythinker_code/agents/default/plan.yaml +42 -0
- pythinker_code/agents/default/review.yaml +47 -0
- pythinker_code/agents/default/security_reviewer.yaml +37 -0
- pythinker_code/agents/default/system.md +192 -0
- pythinker_code/agents/default/verifier.yaml +46 -0
- pythinker_code/agents/okabe/agent.yaml +22 -0
- pythinker_code/agentspec.py +163 -0
- pythinker_code/app.py +847 -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/github_feedback.py +228 -0
- pythinker_code/auth/lm_studio.py +418 -0
- pythinker_code/auth/minimax.py +203 -0
- pythinker_code/auth/oauth.py +1145 -0
- pythinker_code/auth/ollama.py +293 -0
- pythinker_code/auth/openai.py +783 -0
- pythinker_code/auth/opencode_go.py +203 -0
- pythinker_code/auth/openrouter.py +225 -0
- pythinker_code/auth/platforms.py +475 -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 +668 -0
- pythinker_code/background/models.py +118 -0
- pythinker_code/background/store.py +243 -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 +268 -0
- pythinker_code/cli/debug.py +11 -0
- pythinker_code/cli/export.py +322 -0
- pythinker_code/cli/info.py +62 -0
- pythinker_code/cli/mcp.py +362 -0
- pythinker_code/cli/plugin.py +351 -0
- pythinker_code/cli/review.py +74 -0
- pythinker_code/cli/secscan.py +11 -0
- pythinker_code/cli/security_scan.py +35 -0
- pythinker_code/cli/toad.py +74 -0
- pythinker_code/cli/update.py +26 -0
- pythinker_code/cli/vis.py +38 -0
- pythinker_code/cli/web.py +80 -0
- pythinker_code/config.py +511 -0
- pythinker_code/constant.py +33 -0
- pythinker_code/events.py +106 -0
- pythinker_code/exception.py +43 -0
- pythinker_code/extensions.py +151 -0
- pythinker_code/hooks/__init__.py +4 -0
- pythinker_code/hooks/config.py +34 -0
- pythinker_code/hooks/engine.py +383 -0
- pythinker_code/hooks/events.py +190 -0
- pythinker_code/hooks/runner.py +92 -0
- pythinker_code/llm.py +441 -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 +166 -0
- pythinker_code/plugin/tool.py +173 -0
- pythinker_code/prompt_templates.py +181 -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 +552 -0
- pythinker_code/soul/approval.py +267 -0
- pythinker_code/soul/btw.py +220 -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/permission.py +368 -0
- pythinker_code/soul/pythinkersoul.py +1763 -0
- pythinker_code/soul/slash.py +340 -0
- pythinker_code/soul/toolset.py +826 -0
- pythinker_code/subagents/__init__.py +21 -0
- pythinker_code/subagents/builder.py +43 -0
- pythinker_code/subagents/core.py +86 -0
- pythinker_code/subagents/discovery.py +234 -0
- pythinker_code/subagents/git_context.py +172 -0
- pythinker_code/subagents/models.py +56 -0
- pythinker_code/subagents/output.py +71 -0
- pythinker_code/subagents/registry.py +28 -0
- pythinker_code/subagents/runner.py +442 -0
- pythinker_code/subagents/store.py +200 -0
- pythinker_code/telemetry/__init__.py +217 -0
- pythinker_code/telemetry/config.py +113 -0
- pythinker_code/telemetry/crash.py +191 -0
- pythinker_code/telemetry/errors.py +113 -0
- pythinker_code/telemetry/metrics.py +208 -0
- pythinker_code/telemetry/otel.py +303 -0
- pythinker_code/telemetry/sentry.py +212 -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 +326 -0
- pythinker_code/tools/agent/description.md +65 -0
- pythinker_code/tools/ask_user/__init__.py +162 -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 +31 -0
- pythinker_code/tools/file/glob.md +17 -0
- pythinker_code/tools/file/glob.py +163 -0
- pythinker_code/tools/file/grep.md +6 -0
- pythinker_code/tools/file/grep_local.py +904 -0
- pythinker_code/tools/file/plan_mode.py +45 -0
- pythinker_code/tools/file/read.md +16 -0
- pythinker_code/tools/file/read.py +303 -0
- pythinker_code/tools/file/read_media.md +24 -0
- pythinker_code/tools/file/read_media.py +220 -0
- pythinker_code/tools/file/replace.md +7 -0
- pythinker_code/tools/file/replace.py +204 -0
- pythinker_code/tools/file/utils.py +257 -0
- pythinker_code/tools/file/write.md +5 -0
- pythinker_code/tools/file/write.py +186 -0
- pythinker_code/tools/plan/__init__.py +362 -0
- pythinker_code/tools/plan/description.md +29 -0
- pythinker_code/tools/plan/enter.py +193 -0
- pythinker_code/tools/plan/enter_description.md +35 -0
- pythinker_code/tools/plan/handoff.py +69 -0
- pythinker_code/tools/plan/heroes.py +277 -0
- pythinker_code/tools/shell/__init__.py +263 -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 +200 -0
- pythinker_code/tools/web/__init__.py +4 -0
- pythinker_code/tools/web/fetch.md +1 -0
- pythinker_code/tools/web/fetch.py +261 -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 +1806 -0
- pythinker_code/ui/shell/components/__init__.py +110 -0
- pythinker_code/ui/shell/components/base.py +25 -0
- pythinker_code/ui/shell/components/bash_execution.py +249 -0
- pythinker_code/ui/shell/components/bordered_loader.py +62 -0
- pythinker_code/ui/shell/components/diff.py +308 -0
- pythinker_code/ui/shell/components/footer.py +231 -0
- pythinker_code/ui/shell/components/key_hints.py +27 -0
- pythinker_code/ui/shell/components/messages.py +152 -0
- pythinker_code/ui/shell/components/render_utils.py +198 -0
- pythinker_code/ui/shell/components/settings_list.py +369 -0
- pythinker_code/ui/shell/components/special_messages.py +125 -0
- pythinker_code/ui/shell/components/tool_execution.py +261 -0
- pythinker_code/ui/shell/console.py +109 -0
- pythinker_code/ui/shell/debug.py +190 -0
- pythinker_code/ui/shell/echo.py +30 -0
- pythinker_code/ui/shell/export_import.py +117 -0
- pythinker_code/ui/shell/keyboard.py +300 -0
- pythinker_code/ui/shell/keymap.py +84 -0
- pythinker_code/ui/shell/mcp_status.py +112 -0
- pythinker_code/ui/shell/model_picker.py +318 -0
- pythinker_code/ui/shell/oauth.py +273 -0
- pythinker_code/ui/shell/placeholders.py +578 -0
- pythinker_code/ui/shell/prompt.py +2888 -0
- pythinker_code/ui/shell/replay.py +215 -0
- pythinker_code/ui/shell/selector.py +364 -0
- pythinker_code/ui/shell/selectors/__init__.py +38 -0
- pythinker_code/ui/shell/selectors/extension.py +37 -0
- pythinker_code/ui/shell/selectors/oauth.py +63 -0
- pythinker_code/ui/shell/selectors/settings.py +349 -0
- pythinker_code/ui/shell/selectors/show_images.py +29 -0
- pythinker_code/ui/shell/selectors/theme.py +28 -0
- pythinker_code/ui/shell/selectors/thinking.py +42 -0
- pythinker_code/ui/shell/session_picker.py +227 -0
- pythinker_code/ui/shell/setup.py +212 -0
- pythinker_code/ui/shell/slash.py +1433 -0
- pythinker_code/ui/shell/spinner_words.py +222 -0
- pythinker_code/ui/shell/startup.py +32 -0
- pythinker_code/ui/shell/task_browser.py +486 -0
- pythinker_code/ui/shell/tool_renderers/__init__.py +197 -0
- pythinker_code/ui/shell/tool_renderers/_render_utils.py +168 -0
- pythinker_code/ui/shell/tool_renderers/agent.py +140 -0
- pythinker_code/ui/shell/tool_renderers/ask_user.py +93 -0
- pythinker_code/ui/shell/tool_renderers/background.py +144 -0
- pythinker_code/ui/shell/tool_renderers/bash.py +103 -0
- pythinker_code/ui/shell/tool_renderers/edit.py +163 -0
- pythinker_code/ui/shell/tool_renderers/find.py +81 -0
- pythinker_code/ui/shell/tool_renderers/generic.py +60 -0
- pythinker_code/ui/shell/tool_renderers/grep.py +98 -0
- pythinker_code/ui/shell/tool_renderers/plan.py +98 -0
- pythinker_code/ui/shell/tool_renderers/read.py +103 -0
- pythinker_code/ui/shell/tool_renderers/think.py +66 -0
- pythinker_code/ui/shell/tool_renderers/todo.py +164 -0
- pythinker_code/ui/shell/tool_renderers/web.py +128 -0
- pythinker_code/ui/shell/tool_renderers/write.py +102 -0
- pythinker_code/ui/shell/update.py +352 -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 +539 -0
- pythinker_code/ui/shell/visualize/_blocks.py +802 -0
- pythinker_code/ui/shell/visualize/_btw_panel.py +227 -0
- pythinker_code/ui/shell/visualize/_input_router.py +48 -0
- pythinker_code/ui/shell/visualize/_interactive.py +531 -0
- pythinker_code/ui/shell/visualize/_live_view.py +891 -0
- pythinker_code/ui/shell/visualize/_question_panel.py +586 -0
- pythinker_code/ui/shell/visualize/_worklog.py +245 -0
- pythinker_code/ui/theme.py +395 -0
- pythinker_code/ui/tui_config.py +82 -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 +38 -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 +935 -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 +83 -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 +714 -0
- pythinker_code/vis/api/statistics.py +209 -0
- pythinker_code/vis/api/system.py +19 -0
- pythinker_code/vis/app.py +199 -0
- pythinker_code/vis/static/assets/highlighted-body-B3W2YXNL-CY1rtwrX.js +1 -0
- pythinker_code/vis/static/assets/index-DSRInNnm.css +1 -0
- pythinker_code/vis/static/assets/index-DgmTI2M_.js +185 -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 +217 -0
- pythinker_code/web/api/open_in.py +233 -0
- pythinker_code/web/api/sessions.py +1256 -0
- pythinker_code/web/app.py +449 -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-DpSMr1jx.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-DpsahJyV.js +1 -0
- pythinker_code/web/static/assets/architectureDiagram-VXUJARFQ-DqiRv9Eg.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-WgtUvqbp.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-rK0RPuZd.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-B0rlvkH-.js +1 -0
- pythinker_code/web/static/assets/chunk-4BX2VUAB-DIkMuLV-.js +1 -0
- pythinker_code/web/static/assets/chunk-55IACEB6-CORdm4k4.js +1 -0
- pythinker_code/web/static/assets/chunk-B4BG7PRW-D9xDhwHO.js +165 -0
- pythinker_code/web/static/assets/chunk-DI55MBZ5-BDmF9Bh-.js +220 -0
- pythinker_code/web/static/assets/chunk-FMBD7UC4-BCse_HmM.js +15 -0
- pythinker_code/web/static/assets/chunk-QN33PNHL-DCpBmTzA.js +1 -0
- pythinker_code/web/static/assets/chunk-QZHKN3VN-BqLuqobw.js +1 -0
- pythinker_code/web/static/assets/chunk-TZMSLE5B-8K2ogOKS.js +1 -0
- pythinker_code/web/static/assets/clarity-D53aC0YG.js +1 -0
- pythinker_code/web/static/assets/classDiagram-2ON5EDUG-D_ZHSii2.js +1 -0
- pythinker_code/web/static/assets/classDiagram-v2-WZHVMYZB-D_ZHSii2.js +1 -0
- pythinker_code/web/static/assets/clojure-P80f7IUj.js +1 -0
- pythinker_code/web/static/assets/clone-GSXejyY1.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-DWTFYA28.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-BRI7ES-N.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-B6BxUuKW.js +321 -0
- pythinker_code/web/static/assets/d-85-TOEBH.js +1 -0
- pythinker_code/web/static/assets/dagre-6UL2VRFP-Ci5GdWfi.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-0hhAylV4.js +24 -0
- pythinker_code/web/static/assets/diagram-QEK2KX5R-8fxgaW6d.js +43 -0
- pythinker_code/web/static/assets/diagram-S2PKOQOG-FRr0_atE.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-B3T-hJUM.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-D0S3u7ot.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-CHrN2a23.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-CfcXZWg0.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-8jMJwCqE.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-BXrFnzMy.js +153 -0
- pythinker_code/web/static/assets/index-BpoLgcEt.js +1 -0
- pythinker_code/web/static/assets/index-BrfQJnRD.js +5 -0
- pythinker_code/web/static/assets/index-C4gFzubz.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/infoDiagram-WHAUD3N6-DdxonBf3.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-BXf4aQei.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-DLpPPOu8.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-DH73UoAH.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-bAer2-sK.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-CuqbwKXv.js +465 -0
- pythinker_code/web/static/assets/mermaid-mWjccvbQ.js +1 -0
- pythinker_code/web/static/assets/mermaid.core-Nx-rTKiV.js +191 -0
- pythinker_code/web/static/assets/min-DbfD8Ywu.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-C6l761Ue.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-fNg41mT9.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-DJz3Kx87.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-B4SbrfE9.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-CoSUjLAG.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-PjhBNHi3.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-DOwESt8-.js +1 -0
- pythinker_code/web/static/assets/stateDiagram-v2-4FDKWEC3-yl3OHWiP.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-CkCLnAgi.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-CZS5XwTf.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-DkqqHNLh.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 +433 -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 +1072 -0
- pythinker_code/wire/types.py +698 -0
- pythinker_code-0.8.0.dist-info/METADATA +706 -0
- pythinker_code-0.8.0.dist-info/RECORD +790 -0
- pythinker_code-0.8.0.dist-info/WHEEL +4 -0
- pythinker_code-0.8.0.dist-info/entry_points.txt +4 -0
- pythinker_code-0.8.0.dist-info/licenses/LICENSE +202 -0
- pythinker_code-0.8.0.dist-info/licenses/NOTICE +14 -0
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
# pyright: reportUnusedClass=false
|
|
2
|
+
"""Renderable block components for the streaming agent view.
|
|
3
|
+
|
|
4
|
+
Each block receives data via method calls and produces Rich renderables.
|
|
5
|
+
They have no knowledge of the event loop or prompt_toolkit.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import random
|
|
12
|
+
import time
|
|
13
|
+
from collections import deque
|
|
14
|
+
from typing import TYPE_CHECKING, Any, NamedTuple, cast
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from markdown_it import MarkdownIt
|
|
18
|
+
|
|
19
|
+
import streamingjson # type: ignore[reportMissingTypeStubs]
|
|
20
|
+
from rich.console import Console, ConsoleOptions, Group, RenderableType, RenderResult
|
|
21
|
+
from rich.style import Style
|
|
22
|
+
from rich.text import Text
|
|
23
|
+
|
|
24
|
+
from pythinker_code.soul import format_context_status, format_token_count
|
|
25
|
+
from pythinker_code.tools import extract_key_argument
|
|
26
|
+
from pythinker_code.ui.shell.components import ToolExecutionComponent
|
|
27
|
+
from pythinker_code.ui.shell.console import console
|
|
28
|
+
from pythinker_code.ui.shell.tool_renderers import (
|
|
29
|
+
ToolResultPayload,
|
|
30
|
+
get_tool_renderer,
|
|
31
|
+
)
|
|
32
|
+
from pythinker_code.ui.shell.tool_renderers.generic import generic_renderer
|
|
33
|
+
from pythinker_code.ui.shell.visualize._worklog import (
|
|
34
|
+
WorkLogState,
|
|
35
|
+
denied_error,
|
|
36
|
+
render_display_blocks,
|
|
37
|
+
render_worklog_entry,
|
|
38
|
+
tool_style,
|
|
39
|
+
)
|
|
40
|
+
from pythinker_code.ui.theme import tui_rich_style
|
|
41
|
+
from pythinker_code.ui.tui_config import is_card_style
|
|
42
|
+
from pythinker_code.utils.datetime import format_elapsed
|
|
43
|
+
from pythinker_code.utils.rich.columns import BulletColumns
|
|
44
|
+
from pythinker_code.utils.rich.markdown import Markdown
|
|
45
|
+
from pythinker_code.wire.types import (
|
|
46
|
+
MCPStatusSnapshot,
|
|
47
|
+
Notification,
|
|
48
|
+
StatusUpdate,
|
|
49
|
+
ToolCall,
|
|
50
|
+
ToolCallPart,
|
|
51
|
+
ToolResult,
|
|
52
|
+
ToolReturnValue,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
_ELLIPSIS = "..."
|
|
56
|
+
_THINKING_PREVIEW_LINES = 6
|
|
57
|
+
_SELF_CLOSING_BLOCKS = frozenset(("fence", "code_block", "hr", "html_block"))
|
|
58
|
+
MAX_SUBAGENT_TOOL_CALLS_TO_SHOW = 4
|
|
59
|
+
|
|
60
|
+
# Background-agent statuses that mean "still running" — the tool call result
|
|
61
|
+
# has arrived but the spawned agent has not yet finished. Blocks with this
|
|
62
|
+
# status must stay in the Live area so their spinner keeps animating.
|
|
63
|
+
_AGENT_ACTIVE_STATUSES = frozenset({"created", "starting", "running", "awaiting_approval"})
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _is_active_background_agent(tool_name: str, result_text: str) -> bool:
|
|
67
|
+
"""Return True when result_text represents a still-running background Agent."""
|
|
68
|
+
if tool_name != "Agent":
|
|
69
|
+
return False
|
|
70
|
+
values: dict[str, str] = {}
|
|
71
|
+
for line in result_text.splitlines():
|
|
72
|
+
if ":" in line:
|
|
73
|
+
k, _, v = line.partition(":")
|
|
74
|
+
values[k.strip()] = v.strip()
|
|
75
|
+
return values.get("kind") == "agent" and values.get("status") in _AGENT_ACTIVE_STATUSES
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# Animated bullet frames shown after the "Thinking" label. Dots grow in
|
|
79
|
+
# from the left, reach three, then drain out from the left — a continuous
|
|
80
|
+
# rightward flow that loops every ``_BULLET_FRAME_INTERVAL * len(frames)``.
|
|
81
|
+
_BULLET_FRAMES = (". ", ".. ", "...", " ..", " .", " ")
|
|
82
|
+
_BULLET_FRAME_INTERVAL = 0.13 # seconds per frame
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _bullet_frame_for(elapsed: float) -> str:
|
|
86
|
+
"""Select the current bullet frame from wall-clock elapsed time."""
|
|
87
|
+
idx = int(elapsed / _BULLET_FRAME_INTERVAL) % len(_BULLET_FRAMES)
|
|
88
|
+
return _BULLET_FRAMES[idx]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _truncate_to_display_width(line: str, max_width: int) -> str:
|
|
92
|
+
"""Truncate *line* so its terminal display width fits within *max_width*.
|
|
93
|
+
|
|
94
|
+
Uses ``rich.cells.cell_len`` for CJK-aware column width measurement.
|
|
95
|
+
"""
|
|
96
|
+
from rich.cells import cell_len
|
|
97
|
+
|
|
98
|
+
if cell_len(line) <= max_width:
|
|
99
|
+
return line
|
|
100
|
+
ellipsis_width = cell_len(_ELLIPSIS)
|
|
101
|
+
budget = max_width - ellipsis_width
|
|
102
|
+
width = 0
|
|
103
|
+
for i, ch in enumerate(line):
|
|
104
|
+
width += cell_len(ch)
|
|
105
|
+
if width > budget:
|
|
106
|
+
return line[:i] + _ELLIPSIS
|
|
107
|
+
return line
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# Lazy-initialized markdown-it parser for incremental token commitment.
|
|
111
|
+
_md_parser: MarkdownIt | None = None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _get_md_parser() -> MarkdownIt:
|
|
115
|
+
global _md_parser
|
|
116
|
+
if _md_parser is None:
|
|
117
|
+
from markdown_it import MarkdownIt
|
|
118
|
+
|
|
119
|
+
# Match the extensions used by the rendering path (utils/rich/markdown.py)
|
|
120
|
+
# so that block boundaries are detected consistently.
|
|
121
|
+
_md_parser = MarkdownIt().enable("strikethrough").enable("table")
|
|
122
|
+
return _md_parser
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _estimate_tokens(text: str) -> float:
|
|
126
|
+
"""Estimate token count for mixed CJK/Latin text.
|
|
127
|
+
|
|
128
|
+
Returns a **float** so that callers can accumulate across small chunks
|
|
129
|
+
without per-chunk floor truncation (e.g. a 3-char ASCII chunk would
|
|
130
|
+
yield 0 if truncated to int immediately, but 0.75 as float).
|
|
131
|
+
|
|
132
|
+
Heuristics based on common BPE tokenizers (cl100k, o200k):
|
|
133
|
+
- CJK ideographs: ~1.5 tokens per character (often split into 2-byte pieces)
|
|
134
|
+
- Latin / ASCII: ~1 token per 4 characters (words average ~4 chars)
|
|
135
|
+
"""
|
|
136
|
+
cjk = 0
|
|
137
|
+
other = 0
|
|
138
|
+
for ch in text:
|
|
139
|
+
cp = ord(ch)
|
|
140
|
+
if (
|
|
141
|
+
0x4E00 <= cp <= 0x9FFF # CJK Unified Ideographs
|
|
142
|
+
or 0x3400 <= cp <= 0x4DBF # CJK Extension A
|
|
143
|
+
or 0xF900 <= cp <= 0xFAFF # CJK Compatibility Ideographs
|
|
144
|
+
or 0x3000 <= cp <= 0x303F # CJK Symbols and Punctuation
|
|
145
|
+
or 0xFF00 <= cp <= 0xFFEF # Fullwidth Forms
|
|
146
|
+
):
|
|
147
|
+
cjk += 1
|
|
148
|
+
else:
|
|
149
|
+
other += 1
|
|
150
|
+
return cjk * 1.5 + other / 4
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _find_committed_boundary(text: str) -> int | None:
|
|
154
|
+
"""Return the character offset up to which *text* can be safely committed.
|
|
155
|
+
|
|
156
|
+
Uses the incremental token commitment algorithm: parse text into block-level
|
|
157
|
+
tokens via ``markdown-it-py``, confirm all blocks except the last one (which
|
|
158
|
+
may be incomplete due to streaming truncation).
|
|
159
|
+
|
|
160
|
+
Returns ``None`` when there are fewer than 2 blocks (nothing to confirm yet).
|
|
161
|
+
"""
|
|
162
|
+
md = _get_md_parser()
|
|
163
|
+
tokens = md.parse(text)
|
|
164
|
+
|
|
165
|
+
# Collect only TOP-LEVEL block boundaries by tracking nesting depth.
|
|
166
|
+
# Nested tokens (e.g. list_item_open inside bullet_list_open) must not be
|
|
167
|
+
# treated as independent blocks — otherwise lists and blockquotes get split.
|
|
168
|
+
block_maps: list[list[int]] = []
|
|
169
|
+
depth = 0
|
|
170
|
+
for t in tokens:
|
|
171
|
+
if t.nesting == 1:
|
|
172
|
+
if depth == 0 and t.map is not None:
|
|
173
|
+
block_maps.append(t.map)
|
|
174
|
+
depth += 1
|
|
175
|
+
elif t.nesting == -1:
|
|
176
|
+
depth -= 1
|
|
177
|
+
elif depth == 0 and t.type in _SELF_CLOSING_BLOCKS and t.map is not None:
|
|
178
|
+
block_maps.append(t.map)
|
|
179
|
+
|
|
180
|
+
if len(block_maps) < 2:
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
# Convert end-line number to character offset by scanning newlines.
|
|
184
|
+
target_line = block_maps[-2][1]
|
|
185
|
+
offset = 0
|
|
186
|
+
for _ in range(target_line):
|
|
187
|
+
offset = text.index("\n", offset) + 1
|
|
188
|
+
return offset
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _tail_lines(text: str, n: int) -> str:
|
|
192
|
+
"""Extract the last *n* lines from *text* via reverse scanning (O(n))."""
|
|
193
|
+
pos = len(text)
|
|
194
|
+
for _ in range(n):
|
|
195
|
+
pos = text.rfind("\n", 0, pos)
|
|
196
|
+
if pos == -1:
|
|
197
|
+
return text
|
|
198
|
+
return text[pos + 1 :]
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class _ContentBlock:
|
|
202
|
+
"""Streaming content block with incremental markdown commitment.
|
|
203
|
+
|
|
204
|
+
For **composing** (``is_think=False``), confirmed markdown blocks are flushed
|
|
205
|
+
to the terminal permanently via ``console.print()`` as they become complete,
|
|
206
|
+
giving users real-time streaming output. Only the unconfirmed tail remains
|
|
207
|
+
in the transient Rich Live area.
|
|
208
|
+
|
|
209
|
+
For **thinking** (``is_think=True``), the default behavior is to keep the
|
|
210
|
+
raw reasoning text only for token accounting and never render it. The
|
|
211
|
+
Live area shows a compact ``Thinking`` label with an animated bullet
|
|
212
|
+
sequence, elapsed time, token count, and a live tokens/second pulse;
|
|
213
|
+
when the block ends, a one-liner ``Thought for Xs · N tokens`` is
|
|
214
|
+
committed to history in grey italics.
|
|
215
|
+
|
|
216
|
+
When ``show_thinking_stream=True``, the legacy behavior is restored: the
|
|
217
|
+
Live area shows a ``Thinking...`` spinner above a 6-line scrolling preview
|
|
218
|
+
of the raw reasoning text, and the full reasoning markdown is committed
|
|
219
|
+
to history when the block ends.
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
def __init__(self, is_think: bool, *, show_thinking_stream: bool = False):
|
|
223
|
+
self.is_think = is_think
|
|
224
|
+
self._show_thinking_stream = show_thinking_stream
|
|
225
|
+
self.raw_text = ""
|
|
226
|
+
# Accumulated float estimate — avoids per-chunk int truncation.
|
|
227
|
+
self._token_count: float = 0.0
|
|
228
|
+
self._start_time = time.monotonic()
|
|
229
|
+
# Incremental commitment state (composing only).
|
|
230
|
+
self._committed_len = 0
|
|
231
|
+
self._has_printed_bullet = False
|
|
232
|
+
|
|
233
|
+
# -- Public API ----------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
def append(self, content: str) -> None:
|
|
236
|
+
self.raw_text += content
|
|
237
|
+
self._token_count += _estimate_tokens(content)
|
|
238
|
+
# Block boundaries require newlines; skip parse for mid-line chunks.
|
|
239
|
+
if not self.is_think and "\n" in content:
|
|
240
|
+
self._flush_committed()
|
|
241
|
+
|
|
242
|
+
def compose(self) -> RenderableType:
|
|
243
|
+
"""Render the transient Live area content.
|
|
244
|
+
|
|
245
|
+
Thinking mode shows the italic ``Thinking`` label with animated
|
|
246
|
+
bullets; composing mode shows the dots spinner over the
|
|
247
|
+
uncommitted markdown tail. When ``show_thinking_stream`` is enabled,
|
|
248
|
+
thinking mode falls back to the legacy ``Thinking...`` spinner stacked
|
|
249
|
+
above a 6-line scrolling preview of the raw reasoning text.
|
|
250
|
+
"""
|
|
251
|
+
if self.is_think:
|
|
252
|
+
if self._show_thinking_stream:
|
|
253
|
+
return self._compose_thinking_stream()
|
|
254
|
+
return self._compose_thinking()
|
|
255
|
+
return self._compose_spinner()
|
|
256
|
+
|
|
257
|
+
def compose_final(self) -> RenderableType:
|
|
258
|
+
"""Render the remaining uncommitted content when the block ends."""
|
|
259
|
+
if self.is_think:
|
|
260
|
+
if self._show_thinking_stream:
|
|
261
|
+
remaining = self._pending_text()
|
|
262
|
+
if not remaining:
|
|
263
|
+
return Text("")
|
|
264
|
+
return BulletColumns(
|
|
265
|
+
Markdown(remaining, style="grey50 italic"),
|
|
266
|
+
bullet_style="grey50",
|
|
267
|
+
)
|
|
268
|
+
elapsed_str = format_elapsed(time.monotonic() - self._start_time)
|
|
269
|
+
count_str = format_token_count(int(self._token_count))
|
|
270
|
+
return Text(
|
|
271
|
+
f"Thought for {elapsed_str} · {count_str} tokens",
|
|
272
|
+
style="grey50 italic",
|
|
273
|
+
)
|
|
274
|
+
remaining = self._pending_text()
|
|
275
|
+
if not remaining:
|
|
276
|
+
return Text("")
|
|
277
|
+
return self._wrap_bullet(Markdown(remaining))
|
|
278
|
+
|
|
279
|
+
def has_pending(self) -> bool:
|
|
280
|
+
"""Whether there is uncommitted content to flush."""
|
|
281
|
+
# Thinking blocks always commit a final trace line if any content
|
|
282
|
+
# was received, so gate on raw_text rather than uncommitted length.
|
|
283
|
+
if self.is_think:
|
|
284
|
+
return bool(self.raw_text)
|
|
285
|
+
return bool(self._pending_text())
|
|
286
|
+
|
|
287
|
+
# -- Private -------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
def _pending_text(self) -> str:
|
|
290
|
+
return self.raw_text[self._committed_len :]
|
|
291
|
+
|
|
292
|
+
def _wrap_bullet(self, renderable: RenderableType) -> BulletColumns:
|
|
293
|
+
"""First call gets the ``•`` bullet; subsequent calls get a space."""
|
|
294
|
+
if self._has_printed_bullet:
|
|
295
|
+
return BulletColumns(renderable, bullet=Text(" "))
|
|
296
|
+
self._has_printed_bullet = True
|
|
297
|
+
return BulletColumns(renderable)
|
|
298
|
+
|
|
299
|
+
def _flush_committed(self) -> None:
|
|
300
|
+
"""Commit confirmed markdown blocks to permanent terminal output."""
|
|
301
|
+
pending = self._pending_text()
|
|
302
|
+
if not pending:
|
|
303
|
+
return
|
|
304
|
+
boundary = _find_committed_boundary(pending)
|
|
305
|
+
if boundary is None:
|
|
306
|
+
return
|
|
307
|
+
committed_text = pending[:boundary]
|
|
308
|
+
console.print(self._wrap_bullet(Markdown(committed_text)))
|
|
309
|
+
self._committed_len += boundary
|
|
310
|
+
|
|
311
|
+
def _compose_spinner(self) -> Text:
|
|
312
|
+
elapsed = time.monotonic() - self._start_time
|
|
313
|
+
elapsed_str = format_elapsed(elapsed)
|
|
314
|
+
count_str = f"{format_token_count(int(self._token_count))} tokens"
|
|
315
|
+
|
|
316
|
+
glyph = "●" if int(time.monotonic() / 0.8) % 2 == 0 else " "
|
|
317
|
+
line = Text(f"{glyph} ", style=Style(color="grey50"))
|
|
318
|
+
line.append("Composing...")
|
|
319
|
+
line.append(f" {elapsed_str}", style="grey50")
|
|
320
|
+
line.append(f" · {count_str}", style="grey50")
|
|
321
|
+
return line
|
|
322
|
+
|
|
323
|
+
def _compose_thinking_stream(self) -> RenderableType:
|
|
324
|
+
"""Legacy 'Thinking...' spinner stacked over a 6-line scrolling preview."""
|
|
325
|
+
spinner = self._compose_thinking_spinner()
|
|
326
|
+
pending = self._pending_text()
|
|
327
|
+
if not pending:
|
|
328
|
+
return spinner
|
|
329
|
+
preview = self._build_preview(pending)
|
|
330
|
+
return Group(spinner, Text(preview, style="grey50 italic"))
|
|
331
|
+
|
|
332
|
+
def _compose_thinking_spinner(self) -> Text:
|
|
333
|
+
"""Legacy 'Thinking...' header used by the stream-mode preview."""
|
|
334
|
+
elapsed = time.monotonic() - self._start_time
|
|
335
|
+
elapsed_str = format_elapsed(elapsed)
|
|
336
|
+
count_str = f"{format_token_count(int(self._token_count))} tokens"
|
|
337
|
+
glyph = "●" if int(time.monotonic() / 0.8) % 2 == 0 else " "
|
|
338
|
+
line = Text(f"{glyph} ", style=Style(color="grey50"))
|
|
339
|
+
line.append("Thinking...")
|
|
340
|
+
line.append(f" {elapsed_str}", style="grey50")
|
|
341
|
+
line.append(f" · {count_str}", style="grey50")
|
|
342
|
+
return line
|
|
343
|
+
|
|
344
|
+
def _build_preview(self, text: str) -> str:
|
|
345
|
+
"""Tail-trim *text* to the last ``_THINKING_PREVIEW_LINES`` and clamp width."""
|
|
346
|
+
max_width = console.width - 2 if console.width else 78
|
|
347
|
+
tail_text = _tail_lines(text, _THINKING_PREVIEW_LINES)
|
|
348
|
+
lines = tail_text.split("\n")
|
|
349
|
+
return "\n".join(_truncate_to_display_width(line, max_width) for line in lines)
|
|
350
|
+
|
|
351
|
+
def _compose_thinking(self) -> Text:
|
|
352
|
+
"""Render the thinking line: italic Thinking + bullets + metadata."""
|
|
353
|
+
elapsed = time.monotonic() - self._start_time
|
|
354
|
+
elapsed_str = format_elapsed(elapsed)
|
|
355
|
+
tokens_int = int(self._token_count)
|
|
356
|
+
count_str = f"{format_token_count(tokens_int)} tokens"
|
|
357
|
+
frame = _bullet_frame_for(elapsed)
|
|
358
|
+
|
|
359
|
+
parts: list[tuple[str, str | Style]] = [
|
|
360
|
+
("Thinking", "italic"),
|
|
361
|
+
(f" {frame}", "cyan"),
|
|
362
|
+
(f" {elapsed_str}", "grey50"),
|
|
363
|
+
(f" · {count_str}", "grey50"),
|
|
364
|
+
]
|
|
365
|
+
|
|
366
|
+
# Live tok/s pulse — a real heartbeat signal that confirms the model
|
|
367
|
+
# is still streaming even when the raw content is hidden.
|
|
368
|
+
if elapsed > 0.5 and tokens_int > 0:
|
|
369
|
+
rate = int(tokens_int / elapsed)
|
|
370
|
+
if rate > 0:
|
|
371
|
+
parts.append((f" · {rate} tok/s", "grey50"))
|
|
372
|
+
|
|
373
|
+
return Text.assemble(*parts)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
class _ToolCallBlock:
|
|
377
|
+
class FinishedSubCall(NamedTuple):
|
|
378
|
+
call: ToolCall
|
|
379
|
+
result: ToolReturnValue
|
|
380
|
+
|
|
381
|
+
def __init__(self, tool_call: ToolCall):
|
|
382
|
+
self._tool_name = tool_call.function.name
|
|
383
|
+
self._tool_call_id = tool_call.id
|
|
384
|
+
self._lexer = streamingjson.Lexer()
|
|
385
|
+
if tool_call.function.arguments is not None:
|
|
386
|
+
self._lexer.append_string(tool_call.function.arguments)
|
|
387
|
+
|
|
388
|
+
self._argument = self._extract_worklog_argument(
|
|
389
|
+
tool_call.function.arguments, self._tool_name
|
|
390
|
+
)
|
|
391
|
+
self._result: ToolReturnValue | None = None
|
|
392
|
+
self._subagent_id: str | None = None
|
|
393
|
+
self._subagent_type: str | None = None
|
|
394
|
+
|
|
395
|
+
self._ongoing_subagent_tool_calls: dict[str, ToolCall] = {}
|
|
396
|
+
self._last_subagent_tool_call: ToolCall | None = None
|
|
397
|
+
self._n_finished_subagent_tool_calls = 0
|
|
398
|
+
self._finished_subagent_tool_calls = deque[_ToolCallBlock.FinishedSubCall](
|
|
399
|
+
maxlen=MAX_SUBAGENT_TOOL_CALLS_TO_SHOW
|
|
400
|
+
)
|
|
401
|
+
# Pythinker card: lazily built when the tui style is "card" AND a
|
|
402
|
+
# renderer is registered for this tool. Stays None on the legacy
|
|
403
|
+
# ``pythinker`` worklog path so that rendering is bit-for-bit
|
|
404
|
+
# unchanged.
|
|
405
|
+
self._tui_card: ToolExecutionComponent | None = None
|
|
406
|
+
# True while the Agent tool result indicates a still-running background
|
|
407
|
+
# agent. The block stays in _tool_call_blocks (and in the Live area)
|
|
408
|
+
# rather than being flushed to static scrollback, so the spinner keeps
|
|
409
|
+
# animating at the Live refresh rate.
|
|
410
|
+
self._is_background_pending: bool = False
|
|
411
|
+
|
|
412
|
+
self._renderable: RenderableType = self._compose()
|
|
413
|
+
|
|
414
|
+
def compose(self) -> RenderableType:
|
|
415
|
+
# Running tool cards and background-pending Agent cards include live
|
|
416
|
+
# status markers. Recompose them on each Live/prompt refresh.
|
|
417
|
+
if self._result is None or self._is_background_pending:
|
|
418
|
+
return self._compose()
|
|
419
|
+
return self._renderable
|
|
420
|
+
|
|
421
|
+
@property
|
|
422
|
+
def tool_call_id(self) -> str:
|
|
423
|
+
return self._tool_call_id
|
|
424
|
+
|
|
425
|
+
@property
|
|
426
|
+
def finished(self) -> bool:
|
|
427
|
+
return self._result is not None
|
|
428
|
+
|
|
429
|
+
@property
|
|
430
|
+
def is_background_pending(self) -> bool:
|
|
431
|
+
return self._is_background_pending
|
|
432
|
+
|
|
433
|
+
def append_args_part(self, args_part: str):
|
|
434
|
+
if self.finished:
|
|
435
|
+
return
|
|
436
|
+
self._lexer.append_string(args_part)
|
|
437
|
+
# TODO: maybe don't extract detail if it's already stable
|
|
438
|
+
argument = self._extract_worklog_argument(self._lexer.complete_json(), self._tool_name)
|
|
439
|
+
if argument and argument != self._argument:
|
|
440
|
+
self._argument = argument
|
|
441
|
+
self._renderable = self._compose()
|
|
442
|
+
|
|
443
|
+
def finish(self, result: ToolReturnValue):
|
|
444
|
+
self._result = result
|
|
445
|
+
result_text = self._card_result_text(result)
|
|
446
|
+
self._is_background_pending = _is_active_background_agent(self._tool_name, result_text)
|
|
447
|
+
self._renderable = self._compose()
|
|
448
|
+
|
|
449
|
+
def append_sub_tool_call(self, tool_call: ToolCall):
|
|
450
|
+
self._ongoing_subagent_tool_calls[tool_call.id] = tool_call
|
|
451
|
+
self._last_subagent_tool_call = tool_call
|
|
452
|
+
|
|
453
|
+
def append_sub_tool_call_part(self, tool_call_part: ToolCallPart):
|
|
454
|
+
if self._last_subagent_tool_call is None:
|
|
455
|
+
return
|
|
456
|
+
if not tool_call_part.arguments_part:
|
|
457
|
+
return
|
|
458
|
+
if self._last_subagent_tool_call.function.arguments is None:
|
|
459
|
+
self._last_subagent_tool_call.function.arguments = tool_call_part.arguments_part
|
|
460
|
+
else:
|
|
461
|
+
self._last_subagent_tool_call.function.arguments += tool_call_part.arguments_part
|
|
462
|
+
|
|
463
|
+
def finish_sub_tool_call(self, tool_result: ToolResult):
|
|
464
|
+
self._last_subagent_tool_call = None
|
|
465
|
+
sub_tool_call = self._ongoing_subagent_tool_calls.pop(tool_result.tool_call_id, None)
|
|
466
|
+
if sub_tool_call is None:
|
|
467
|
+
return
|
|
468
|
+
|
|
469
|
+
self._finished_subagent_tool_calls.append(
|
|
470
|
+
_ToolCallBlock.FinishedSubCall(
|
|
471
|
+
call=sub_tool_call,
|
|
472
|
+
result=tool_result.return_value,
|
|
473
|
+
)
|
|
474
|
+
)
|
|
475
|
+
self._n_finished_subagent_tool_calls += 1
|
|
476
|
+
self._renderable = self._compose()
|
|
477
|
+
|
|
478
|
+
def set_subagent_metadata(self, agent_id: str, subagent_type: str) -> None:
|
|
479
|
+
changed = (self._subagent_id, self._subagent_type) != (agent_id, subagent_type)
|
|
480
|
+
self._subagent_id = agent_id
|
|
481
|
+
self._subagent_type = subagent_type
|
|
482
|
+
if changed:
|
|
483
|
+
self._renderable = self._compose()
|
|
484
|
+
|
|
485
|
+
def _compose(self) -> RenderableType:
|
|
486
|
+
if is_card_style():
|
|
487
|
+
card_rendered = self._compose_card()
|
|
488
|
+
if card_rendered is not None:
|
|
489
|
+
return card_rendered
|
|
490
|
+
children: list[RenderableType] = []
|
|
491
|
+
if self._subagent_id is not None and self._subagent_type is not None:
|
|
492
|
+
children.append(
|
|
493
|
+
BulletColumns(
|
|
494
|
+
Text(
|
|
495
|
+
f"subagent {self._subagent_type} ({self._subagent_id})",
|
|
496
|
+
style="grey50",
|
|
497
|
+
),
|
|
498
|
+
bullet_style="grey50",
|
|
499
|
+
)
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
style = tool_style(self._tool_name)
|
|
503
|
+
if style.label == "Subagent" and self._result is not None:
|
|
504
|
+
if self._n_finished_subagent_tool_calls:
|
|
505
|
+
summary = Text(
|
|
506
|
+
f"{self._n_finished_subagent_tool_calls} tool calls completed",
|
|
507
|
+
style="grey50",
|
|
508
|
+
)
|
|
509
|
+
if self._finished_subagent_tool_calls:
|
|
510
|
+
summary.append(
|
|
511
|
+
f" · {len(self._finished_subagent_tool_calls)} recent tracked",
|
|
512
|
+
style="grey50",
|
|
513
|
+
)
|
|
514
|
+
children.append(BulletColumns(summary, bullet_style="grey50"))
|
|
515
|
+
elif self._n_finished_subagent_tool_calls > MAX_SUBAGENT_TOOL_CALLS_TO_SHOW:
|
|
516
|
+
n_hidden = self._n_finished_subagent_tool_calls - MAX_SUBAGENT_TOOL_CALLS_TO_SHOW
|
|
517
|
+
children.append(
|
|
518
|
+
BulletColumns(
|
|
519
|
+
Text(
|
|
520
|
+
f"{n_hidden} more tool call{'s' if n_hidden > 1 else ''} ...",
|
|
521
|
+
style="grey50 italic",
|
|
522
|
+
),
|
|
523
|
+
bullet_style="grey50",
|
|
524
|
+
)
|
|
525
|
+
)
|
|
526
|
+
if not (style.label == "Subagent" and self._result is not None):
|
|
527
|
+
for sub_call, sub_result in self._finished_subagent_tool_calls:
|
|
528
|
+
argument = extract_key_argument(
|
|
529
|
+
sub_call.function.arguments or "", sub_call.function.name
|
|
530
|
+
)
|
|
531
|
+
sub_url = self._extract_full_url(
|
|
532
|
+
sub_call.function.arguments, sub_call.function.name
|
|
533
|
+
)
|
|
534
|
+
sub_text = Text()
|
|
535
|
+
sub_text.append("Used ")
|
|
536
|
+
sub_text.append(sub_call.function.name, style="blue")
|
|
537
|
+
if argument:
|
|
538
|
+
sub_text.append(" (", style="grey50")
|
|
539
|
+
arg_style = Style(color="grey50", link=sub_url) if sub_url else "grey50"
|
|
540
|
+
sub_text.append(argument, style=arg_style)
|
|
541
|
+
sub_text.append(")", style="grey50")
|
|
542
|
+
children.append(
|
|
543
|
+
BulletColumns(
|
|
544
|
+
sub_text,
|
|
545
|
+
bullet_style="green" if not sub_result.is_error else "dark_red",
|
|
546
|
+
)
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
if self._result is None:
|
|
550
|
+
return render_worklog_entry(
|
|
551
|
+
label=style.label,
|
|
552
|
+
target=self._argument,
|
|
553
|
+
state=WorkLogState.RUNNING,
|
|
554
|
+
icon=style.icon,
|
|
555
|
+
icon_style=style.style,
|
|
556
|
+
children=children,
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
error_message = self._result.message if self._result.is_error else ""
|
|
560
|
+
if self._result.is_error and not error_message:
|
|
561
|
+
error_message = getattr(self._result, "brief", "") or "Tool failed"
|
|
562
|
+
state = (
|
|
563
|
+
WorkLogState.DENIED
|
|
564
|
+
if self._result.is_error and denied_error(error_message)
|
|
565
|
+
else WorkLogState.FAILED
|
|
566
|
+
if self._result.is_error
|
|
567
|
+
else WorkLogState.COMPLETED
|
|
568
|
+
)
|
|
569
|
+
children.extend(
|
|
570
|
+
render_display_blocks(
|
|
571
|
+
getattr(self._result, "display", []) or [], is_error=self._result.is_error
|
|
572
|
+
)
|
|
573
|
+
)
|
|
574
|
+
return render_worklog_entry(
|
|
575
|
+
label=style.label,
|
|
576
|
+
target=self._argument,
|
|
577
|
+
state=state,
|
|
578
|
+
detail=error_message if self._result.is_error else None,
|
|
579
|
+
icon=style.icon,
|
|
580
|
+
icon_style=style.style,
|
|
581
|
+
children=children,
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
def _compose_card(self) -> RenderableType | None:
|
|
585
|
+
"""Build/update the Pythinker card. Returns None to fall through.
|
|
586
|
+
|
|
587
|
+
Renderer resolution: prefer a tool-specific renderer registered
|
|
588
|
+
under ``tool_name``; fall back to the generic renderer so any
|
|
589
|
+
tool gets a Pythinker card under the flag. Returns None only if
|
|
590
|
+
the generic renderer itself is missing (i.e. the built-ins were
|
|
591
|
+
never registered).
|
|
592
|
+
"""
|
|
593
|
+
definition = get_tool_renderer(self._tool_name)
|
|
594
|
+
if definition is None:
|
|
595
|
+
definition = generic_renderer()
|
|
596
|
+
if self._tui_card is None:
|
|
597
|
+
self._tui_card = ToolExecutionComponent(
|
|
598
|
+
self._tool_name,
|
|
599
|
+
self._tool_call_id,
|
|
600
|
+
definition=definition,
|
|
601
|
+
)
|
|
602
|
+
# We see the tool call event, so the model has begun work.
|
|
603
|
+
self._tui_card.mark_execution_started()
|
|
604
|
+
raw_args = self._lexer.complete_json() or "{}"
|
|
605
|
+
try:
|
|
606
|
+
parsed = json.loads(raw_args, strict=False)
|
|
607
|
+
except json.JSONDecodeError:
|
|
608
|
+
parsed = {}
|
|
609
|
+
if isinstance(parsed, dict):
|
|
610
|
+
self._tui_card.update_args(cast(dict[str, Any], parsed))
|
|
611
|
+
# Args are complete once a result lands; before that we treat
|
|
612
|
+
# complete_json output as best-effort.
|
|
613
|
+
if self._result is not None:
|
|
614
|
+
self._tui_card.set_args_complete()
|
|
615
|
+
self._tui_card.set_result(
|
|
616
|
+
ToolResultPayload(
|
|
617
|
+
text=self._card_result_text(self._result),
|
|
618
|
+
is_error=self._result.is_error,
|
|
619
|
+
),
|
|
620
|
+
is_partial=self._is_background_pending,
|
|
621
|
+
)
|
|
622
|
+
return self._tui_card.render()
|
|
623
|
+
|
|
624
|
+
@staticmethod
|
|
625
|
+
def _card_result_text(result: ToolReturnValue) -> str:
|
|
626
|
+
"""Flatten a ToolReturnValue to a single text payload for cards.
|
|
627
|
+
|
|
628
|
+
Tool renderers expect the *primary content* (file body, command
|
|
629
|
+
output, grep matches) — that lives in ``output`` for Pythinker.
|
|
630
|
+
Fall back to ``message`` (e.g. "Successfully wrote N bytes" from
|
|
631
|
+
WriteFile, where ``output`` is empty) and finally ``brief`` for
|
|
632
|
+
tools that only emit a summary block. Non-string outputs are
|
|
633
|
+
skipped here; specialized renderers should pull richer detail
|
|
634
|
+
from ``ctx.args``.
|
|
635
|
+
"""
|
|
636
|
+
if result.is_error:
|
|
637
|
+
parts: list[str] = []
|
|
638
|
+
if result.message:
|
|
639
|
+
parts.append(result.message)
|
|
640
|
+
if isinstance(result.output, str) and result.output:
|
|
641
|
+
parts.append(result.output)
|
|
642
|
+
if not parts:
|
|
643
|
+
brief = getattr(result, "brief", "") or "Tool failed"
|
|
644
|
+
parts.append(brief)
|
|
645
|
+
return "\n\n".join(parts)
|
|
646
|
+
if isinstance(result.output, str) and result.output:
|
|
647
|
+
return result.output
|
|
648
|
+
if result.message:
|
|
649
|
+
return result.message
|
|
650
|
+
return getattr(result, "brief", "") or ""
|
|
651
|
+
|
|
652
|
+
@staticmethod
|
|
653
|
+
def _extract_worklog_argument(arguments: str | None, tool_name: str) -> str | None:
|
|
654
|
+
argument = extract_key_argument(arguments or "", tool_name)
|
|
655
|
+
try:
|
|
656
|
+
args = json.loads(arguments or "{}", strict=False)
|
|
657
|
+
except json.JSONDecodeError:
|
|
658
|
+
return argument
|
|
659
|
+
if not isinstance(args, dict):
|
|
660
|
+
return argument
|
|
661
|
+
args = cast(dict[str, Any], args)
|
|
662
|
+
match tool_name:
|
|
663
|
+
case "ReadFile":
|
|
664
|
+
path = args.get("path") or args.get("file_path")
|
|
665
|
+
return str(path) if path else argument
|
|
666
|
+
case _:
|
|
667
|
+
return argument
|
|
668
|
+
|
|
669
|
+
@staticmethod
|
|
670
|
+
def _extract_full_url(arguments: str | None, tool_name: str) -> str | None:
|
|
671
|
+
"""Extract the full URL from FetchURL tool arguments."""
|
|
672
|
+
if tool_name != "FetchURL" or not arguments:
|
|
673
|
+
return None
|
|
674
|
+
try:
|
|
675
|
+
args = json.loads(arguments, strict=False)
|
|
676
|
+
except (json.JSONDecodeError, TypeError):
|
|
677
|
+
return None
|
|
678
|
+
if isinstance(args, dict):
|
|
679
|
+
url = cast(dict[str, Any], args).get("url")
|
|
680
|
+
if url:
|
|
681
|
+
return str(url)
|
|
682
|
+
return None
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
class _NotificationBlock:
|
|
686
|
+
_SEVERITY_STYLE = {
|
|
687
|
+
"info": "cyan",
|
|
688
|
+
"success": "green",
|
|
689
|
+
"warning": "yellow",
|
|
690
|
+
"error": "red",
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
def __init__(self, notification: Notification):
|
|
694
|
+
self.notification = notification
|
|
695
|
+
|
|
696
|
+
def compose(self) -> RenderableType:
|
|
697
|
+
style = self._SEVERITY_STYLE.get(self.notification.severity, "cyan")
|
|
698
|
+
lines: list[RenderableType] = [Text(self.notification.title, style=f"bold {style}")]
|
|
699
|
+
body = self.notification.body.strip()
|
|
700
|
+
if body:
|
|
701
|
+
body_lines = body.splitlines()
|
|
702
|
+
preview = "\n".join(body_lines[:2])
|
|
703
|
+
if len(body_lines) > 2:
|
|
704
|
+
preview += "\n..."
|
|
705
|
+
lines.append(Text(preview, style="grey50"))
|
|
706
|
+
return BulletColumns(Group(*lines), bullet_style=style)
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
class _StatusBlock:
|
|
710
|
+
def __init__(self, initial: StatusUpdate) -> None:
|
|
711
|
+
self.text = Text("", justify="right")
|
|
712
|
+
self._context_usage: float = 0.0
|
|
713
|
+
self._context_tokens: int = 0
|
|
714
|
+
self._max_context_tokens: int = 0
|
|
715
|
+
self._mcp_status: MCPStatusSnapshot | None = None
|
|
716
|
+
self.update(initial)
|
|
717
|
+
|
|
718
|
+
def render(self) -> RenderableType:
|
|
719
|
+
return self.text
|
|
720
|
+
|
|
721
|
+
def update(self, status: StatusUpdate) -> None:
|
|
722
|
+
if status.context_usage is not None:
|
|
723
|
+
self._context_usage = status.context_usage
|
|
724
|
+
if status.context_tokens is not None:
|
|
725
|
+
self._context_tokens = status.context_tokens
|
|
726
|
+
if status.max_context_tokens is not None:
|
|
727
|
+
self._max_context_tokens = status.max_context_tokens
|
|
728
|
+
if status.mcp_status is not None:
|
|
729
|
+
self._mcp_status = status.mcp_status
|
|
730
|
+
if status.context_usage is not None or status.mcp_status is not None:
|
|
731
|
+
parts: list[str] = []
|
|
732
|
+
if self._context_usage or self._max_context_tokens:
|
|
733
|
+
parts.append(
|
|
734
|
+
format_context_status(
|
|
735
|
+
self._context_usage,
|
|
736
|
+
self._context_tokens,
|
|
737
|
+
self._max_context_tokens,
|
|
738
|
+
)
|
|
739
|
+
)
|
|
740
|
+
if self._mcp_status is not None and self._mcp_status.loading:
|
|
741
|
+
parts.append(
|
|
742
|
+
f"MCP {self._mcp_status.connected}/{self._mcp_status.total} · "
|
|
743
|
+
f"{self._mcp_status.tools} tools"
|
|
744
|
+
)
|
|
745
|
+
self.text.plain = " ".join(parts)
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
class _CompactionBlock:
|
|
749
|
+
"""Animated compaction progress with a time-based estimate.
|
|
750
|
+
|
|
751
|
+
The bar fills toward 95% over ``EXPECTED_DURATION_S`` (compaction has no
|
|
752
|
+
real progress signal), then disappears once ``CompactionEnd`` arrives.
|
|
753
|
+
"""
|
|
754
|
+
|
|
755
|
+
BAR_WIDTH = 40
|
|
756
|
+
EXPECTED_DURATION_S = 60.0
|
|
757
|
+
MAX_ESTIMATED_PROGRESS = 0.95
|
|
758
|
+
|
|
759
|
+
TIPS: tuple[str, ...] = (
|
|
760
|
+
"Shift+Tab toggles plan mode for multi-step work",
|
|
761
|
+
"Subagents keep your main context clean",
|
|
762
|
+
"/verify before declaring work done",
|
|
763
|
+
"/learn captures a lesson after a correction",
|
|
764
|
+
"@-mention files to attach them to the next message",
|
|
765
|
+
"/feedback sends a note to the Pythinker team",
|
|
766
|
+
"/theme switches between dark and light",
|
|
767
|
+
"Ctrl+O opens your $EDITOR for long messages",
|
|
768
|
+
"Use /resume to pick up a previous session",
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
def __init__(self) -> None:
|
|
772
|
+
self._start = time.monotonic()
|
|
773
|
+
self._tip = random.choice(self.TIPS)
|
|
774
|
+
|
|
775
|
+
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
|
776
|
+
yield from console.render(self._render(), options)
|
|
777
|
+
|
|
778
|
+
def _render(self) -> RenderableType:
|
|
779
|
+
elapsed = max(0.0, time.monotonic() - self._start)
|
|
780
|
+
progress = min(elapsed / self.EXPECTED_DURATION_S, self.MAX_ESTIMATED_PROGRESS)
|
|
781
|
+
filled = int(round(progress * self.BAR_WIDTH))
|
|
782
|
+
empty = self.BAR_WIDTH - filled
|
|
783
|
+
pct = int(progress * 100)
|
|
784
|
+
accent = tui_rich_style("accent")
|
|
785
|
+
muted = tui_rich_style("muted")
|
|
786
|
+
subtle = tui_rich_style("dim")
|
|
787
|
+
|
|
788
|
+
title = Text()
|
|
789
|
+
glyph = "●" if int(time.monotonic() / 0.8) % 2 == 0 else " "
|
|
790
|
+
title.append(f"{glyph} ", style=tui_rich_style("muted"))
|
|
791
|
+
title.append("Compacting conversation…")
|
|
792
|
+
title.append(f" ({format_elapsed(elapsed)})", style=subtle)
|
|
793
|
+
|
|
794
|
+
bar = Text(" ")
|
|
795
|
+
bar.append("▰" * filled, style=accent)
|
|
796
|
+
bar.append("▱" * empty, style=muted)
|
|
797
|
+
bar.append(f" {pct}%", style=accent + Style(bold=True))
|
|
798
|
+
|
|
799
|
+
tip = Text(" ⎿ ", style=muted)
|
|
800
|
+
tip.append(f"Tip: {self._tip}", style=subtle)
|
|
801
|
+
|
|
802
|
+
return Group(title, bar, tip)
|