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,211 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Telemetry event tracking for pythinker-code.
|
|
3
|
+
|
|
4
|
+
This module has NO dependencies on other pythinker_code modules to avoid import cycles.
|
|
5
|
+
track() can be called at any point during startup, even before the sink is attached.
|
|
6
|
+
Events are buffered in memory and flushed once the sink is ready.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
from pythinker_code.telemetry import track, set_context, attach_sink
|
|
10
|
+
|
|
11
|
+
# Early in startup — events queue in memory
|
|
12
|
+
track("first_launch")
|
|
13
|
+
|
|
14
|
+
# After app init — attach sink to start flushing
|
|
15
|
+
set_context(device_id="abc", session_id="def")
|
|
16
|
+
attach_sink(sink)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import asyncio
|
|
22
|
+
import atexit
|
|
23
|
+
import time
|
|
24
|
+
import uuid
|
|
25
|
+
from collections import deque
|
|
26
|
+
from contextlib import suppress
|
|
27
|
+
from typing import TYPE_CHECKING, Any
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from pythinker_code.telemetry.sink import EventSink
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Module-level state (zero dependencies)
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
_MAX_QUEUE_SIZE = 1000
|
|
37
|
+
"""Maximum number of events to buffer before sink is attached."""
|
|
38
|
+
|
|
39
|
+
_event_queue: deque[dict[str, Any]] = deque(maxlen=_MAX_QUEUE_SIZE)
|
|
40
|
+
"""Events buffered before sink is attached."""
|
|
41
|
+
|
|
42
|
+
_device_id: str | None = None
|
|
43
|
+
_session_id: str | None = None
|
|
44
|
+
_client_info: tuple[str, str | None] | None = None
|
|
45
|
+
"""(name, version) tuple, set atomically via set_client_info."""
|
|
46
|
+
_session_started_sessions: set[str] = set()
|
|
47
|
+
"""Session ids that already emitted the session_started event in this process."""
|
|
48
|
+
_sink: EventSink | None = None
|
|
49
|
+
_disabled: bool = False
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def set_context(*, device_id: str, session_id: str) -> None:
|
|
53
|
+
"""Set device and session identifiers. Call once after app init."""
|
|
54
|
+
global _device_id, _session_id
|
|
55
|
+
_device_id = device_id
|
|
56
|
+
_session_id = session_id
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def set_client_info(*, name: str, version: str | None = None) -> None:
|
|
60
|
+
"""Set the wire/acp client name and version (e.g. VSCode 1.90.0, zed 0.180.0).
|
|
61
|
+
|
|
62
|
+
Called by wire/acp servers after receiving the client's initialize message.
|
|
63
|
+
Values are passed through verbatim — backend is responsible for any
|
|
64
|
+
validation, normalization or alerting on anomalous values.
|
|
65
|
+
"""
|
|
66
|
+
global _client_info
|
|
67
|
+
if not name:
|
|
68
|
+
return
|
|
69
|
+
_client_info = (name, version)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_client_info() -> tuple[str, str | None] | None:
|
|
73
|
+
"""Return the current (name, version) tuple, or None if unset.
|
|
74
|
+
|
|
75
|
+
Used by session-level telemetry to attribute wire/acp sessions.
|
|
76
|
+
"""
|
|
77
|
+
return _client_info
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def track_session_started_once(
|
|
81
|
+
*,
|
|
82
|
+
ui_mode: str,
|
|
83
|
+
resumed: bool,
|
|
84
|
+
client_name: str | None = None,
|
|
85
|
+
client_version: str | None = None,
|
|
86
|
+
) -> None:
|
|
87
|
+
"""Emit one session_started event for the current session in this process."""
|
|
88
|
+
session_id = _session_id
|
|
89
|
+
if not session_id or session_id in _session_started_sessions:
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
ui = (ui_mode or "unknown").strip().lower()
|
|
93
|
+
name = client_name
|
|
94
|
+
version = client_version
|
|
95
|
+
if name is None and ui in {"wire", "acp"}:
|
|
96
|
+
client_info = get_client_info()
|
|
97
|
+
if client_info is not None:
|
|
98
|
+
name, version = client_info
|
|
99
|
+
if not name:
|
|
100
|
+
name = ui or "unknown"
|
|
101
|
+
|
|
102
|
+
_session_started_sessions.add(session_id)
|
|
103
|
+
track(
|
|
104
|
+
"session_started",
|
|
105
|
+
client_name=name,
|
|
106
|
+
client_version=version,
|
|
107
|
+
ui_mode=ui,
|
|
108
|
+
resumed=resumed,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if _sink is not None:
|
|
112
|
+
with suppress(Exception):
|
|
113
|
+
asyncio.get_running_loop().create_task(_sink.flush())
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def disable() -> None:
|
|
117
|
+
"""Permanently disable telemetry for this process. Events are silently dropped."""
|
|
118
|
+
global _disabled
|
|
119
|
+
_disabled = True
|
|
120
|
+
_event_queue.clear()
|
|
121
|
+
if _sink is not None:
|
|
122
|
+
_sink.clear_buffer()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def attach_sink(sink: EventSink) -> None:
|
|
126
|
+
"""Attach the event sink and drain any queued events.
|
|
127
|
+
|
|
128
|
+
Multi-session ACP mode calls ``PythinkerCLI.create()`` per session, which
|
|
129
|
+
means ``attach_sink`` runs again while a previous sink may hold
|
|
130
|
+
un-flushed buffered events. Flush the old sink synchronously (writes
|
|
131
|
+
any pending events to the disk fallback) before replacing it, so
|
|
132
|
+
earlier sessions' events are not silently orphaned.
|
|
133
|
+
"""
|
|
134
|
+
global _sink
|
|
135
|
+
if _sink is not None and _sink is not sink:
|
|
136
|
+
# flush_sync already swallows its own transport failures;
|
|
137
|
+
# ``suppress`` guards against truly unexpected errors so sink
|
|
138
|
+
# replacement is never blocked by a flaky predecessor.
|
|
139
|
+
with suppress(Exception):
|
|
140
|
+
_sink.flush_sync()
|
|
141
|
+
_sink = sink
|
|
142
|
+
# Drain events that were queued before sink was ready,
|
|
143
|
+
# backfilling device_id/session_id for events tracked before set_context().
|
|
144
|
+
if _event_queue:
|
|
145
|
+
for event in _event_queue:
|
|
146
|
+
if event.get("device_id") is None:
|
|
147
|
+
event["device_id"] = _device_id
|
|
148
|
+
if event.get("session_id") is None:
|
|
149
|
+
event["session_id"] = _session_id
|
|
150
|
+
_sink.accept(event)
|
|
151
|
+
_event_queue.clear()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def track(event: str, **properties: bool | int | float | str | None) -> None:
|
|
155
|
+
"""
|
|
156
|
+
Record a telemetry event.
|
|
157
|
+
|
|
158
|
+
This function is non-blocking: it appends to an in-memory list.
|
|
159
|
+
Safe to call from synchronous prompt_toolkit key handlers.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
event: Event name (e.g. "input_command").
|
|
163
|
+
**properties: Event properties. String values should only be used for
|
|
164
|
+
known enum-like values (command names, mode names, error types).
|
|
165
|
+
NEVER pass user input, file paths, or code snippets.
|
|
166
|
+
"""
|
|
167
|
+
if _disabled:
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
record = {
|
|
171
|
+
"event_id": uuid.uuid4().hex,
|
|
172
|
+
"device_id": _device_id,
|
|
173
|
+
"session_id": _session_id,
|
|
174
|
+
"event": event,
|
|
175
|
+
"timestamp": time.time(),
|
|
176
|
+
"properties": properties if properties else {},
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if _sink is not None:
|
|
180
|
+
_sink.accept(record)
|
|
181
|
+
else:
|
|
182
|
+
_event_queue.append(record)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def get_sink() -> EventSink | None:
|
|
186
|
+
"""Return the current sink, or None if not attached."""
|
|
187
|
+
return _sink
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def flush_sync() -> None:
|
|
191
|
+
"""Synchronously flush any buffered events. Called on exit.
|
|
192
|
+
|
|
193
|
+
Drains the in-process buffer first (so any pending ``track()`` events get
|
|
194
|
+
handed to the OTel exporter), then waits for both Sentry and OTel to
|
|
195
|
+
finish their network round-trips.
|
|
196
|
+
"""
|
|
197
|
+
if _sink is not None:
|
|
198
|
+
_sink.flush_sync()
|
|
199
|
+
# Flush vendor SDKs last — they take the network hit.
|
|
200
|
+
try:
|
|
201
|
+
from pythinker_code.telemetry import otel as _otel
|
|
202
|
+
from pythinker_code.telemetry import sentry as _sentry
|
|
203
|
+
|
|
204
|
+
_sentry.flush(timeout_seconds=2.0)
|
|
205
|
+
_otel.shutdown(timeout_millis=2000)
|
|
206
|
+
except Exception:
|
|
207
|
+
pass
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# Register atexit handler to flush remaining events on normal exit
|
|
211
|
+
atexit.register(flush_sync)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Telemetry endpoint configuration.
|
|
2
|
+
|
|
3
|
+
Defaults point at the pythinker-operated Bugsink + SigNoz infrastructure.
|
|
4
|
+
Sentry/Bugsink DSNs are designed to be public; the OTLP bearer token is embedded
|
|
5
|
+
following the same industry convention used by Datadog RUM and PostHog public
|
|
6
|
+
keys, mitigated server-side by rate limiting and PII scrubbing at the edge
|
|
7
|
+
collector.
|
|
8
|
+
|
|
9
|
+
Override any value at runtime with the matching environment variable. Disable
|
|
10
|
+
telemetry entirely with ``PYTHINKER_DISABLE_TELEMETRY=1``.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
# Bugsink (Sentry-protocol error tracking)
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
DEFAULT_SENTRY_DSN = "https://ab578ebdf2f24c279d9e866ee190574c@errors.pythinker.com/1"
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# SigNoz via the pythinker edge OTel collector
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
DEFAULT_OTEL_ENDPOINT = "https://otel.pythinker.com"
|
|
28
|
+
DEFAULT_OTEL_INGEST_TOKEN = "83e2d8f0cb72c6c0f8896b40cf68de6e67bfad895a61729b36bc27e594d66d69"
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Resolution
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def sentry_dsn() -> str:
|
|
36
|
+
"""Resolve the Sentry/Bugsink DSN, honoring env override and explicit empty."""
|
|
37
|
+
return os.environ.get("PYTHINKER_SENTRY_DSN", DEFAULT_SENTRY_DSN)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def otel_endpoint() -> str:
|
|
41
|
+
"""Resolve the OTLP HTTP endpoint base URL (no trailing slash)."""
|
|
42
|
+
return os.environ.get("PYTHINKER_OTEL_ENDPOINT", DEFAULT_OTEL_ENDPOINT).rstrip("/")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def otel_ingest_token() -> str:
|
|
46
|
+
"""Resolve the bearer token presented to the edge collector."""
|
|
47
|
+
return os.environ.get("PYTHINKER_OTEL_TOKEN", DEFAULT_OTEL_INGEST_TOKEN)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def is_disabled() -> bool:
|
|
51
|
+
"""Master kill switch. ``PYTHINKER_DISABLE_TELEMETRY=1`` (or ``true``) disables both
|
|
52
|
+
Sentry and OTel emission for the process."""
|
|
53
|
+
raw = os.environ.get("PYTHINKER_DISABLE_TELEMETRY", "").strip().lower()
|
|
54
|
+
return raw in {"1", "true", "yes", "on"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Crash telemetry: capture uncaught exceptions via sys.excepthook and
|
|
2
|
+
asyncio's exception handler, emit a ``crash`` event for in-process counting,
|
|
3
|
+
forward the full exception to Sentry/Bugsink for debugging, then delegate to
|
|
4
|
+
the original handler so the traceback still gets printed.
|
|
5
|
+
|
|
6
|
+
Privacy posture:
|
|
7
|
+
- The OTel ``crash`` event still carries only the exception *class name*,
|
|
8
|
+
phase, and source — same as before.
|
|
9
|
+
- The Sentry capture path includes the stack trace (path-scrubbed by the
|
|
10
|
+
sentry module's ``before_send`` hook).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import sys
|
|
17
|
+
from types import TracebackType
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# Phase tracking
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
_phase: str = "startup"
|
|
25
|
+
"""Coarse lifecycle bucket recorded on each crash event.
|
|
26
|
+
|
|
27
|
+
Valid values: ``startup`` (before app init finishes), ``runtime``
|
|
28
|
+
(normal operation), ``shutdown`` (after the main entry point returns).
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def set_phase(phase: str) -> None:
|
|
33
|
+
"""Update the current lifecycle phase. Called by app entry points."""
|
|
34
|
+
global _phase
|
|
35
|
+
_phase = phase
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Filters
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _should_ignore_for_excepthook(exc_type: type[BaseException]) -> bool:
|
|
44
|
+
"""Return True for exceptions that are not programming bugs.
|
|
45
|
+
|
|
46
|
+
- KeyboardInterrupt: Ctrl+C, already covered by the ``cancel`` event.
|
|
47
|
+
- SystemExit: deliberate exit, not a crash.
|
|
48
|
+
- click.ClickException (UsageError / BadParameter / ...): user-facing
|
|
49
|
+
CLI input errors, not program bugs.
|
|
50
|
+
"""
|
|
51
|
+
if issubclass(exc_type, (KeyboardInterrupt, SystemExit)):
|
|
52
|
+
return True
|
|
53
|
+
try:
|
|
54
|
+
import click
|
|
55
|
+
|
|
56
|
+
if issubclass(exc_type, click.exceptions.ClickException):
|
|
57
|
+
return True
|
|
58
|
+
except ImportError:
|
|
59
|
+
pass
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# ---------------------------------------------------------------------------
|
|
64
|
+
# sys.excepthook
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
_original_excepthook: Any = None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _excepthook(
|
|
71
|
+
exc_type: type[BaseException],
|
|
72
|
+
exc: BaseException,
|
|
73
|
+
tb: TracebackType | None,
|
|
74
|
+
) -> None:
|
|
75
|
+
if not _should_ignore_for_excepthook(exc_type):
|
|
76
|
+
# Any failure inside telemetry must not mask the underlying crash.
|
|
77
|
+
try:
|
|
78
|
+
from pythinker_code.telemetry import sentry as _sentry
|
|
79
|
+
from pythinker_code.telemetry import track
|
|
80
|
+
|
|
81
|
+
track(
|
|
82
|
+
"crash",
|
|
83
|
+
error_type=exc_type.__name__,
|
|
84
|
+
where=_phase,
|
|
85
|
+
source="excepthook",
|
|
86
|
+
)
|
|
87
|
+
_sentry.capture_exception(exc)
|
|
88
|
+
except Exception:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
# Always delegate so the traceback is still printed.
|
|
92
|
+
handler = _original_excepthook if _original_excepthook is not None else sys.__excepthook__
|
|
93
|
+
handler(exc_type, exc, tb)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def install_crash_handlers() -> None:
|
|
97
|
+
"""Install the process-level ``sys.excepthook``.
|
|
98
|
+
|
|
99
|
+
Idempotent: repeated calls are no-ops. Should be called as early as
|
|
100
|
+
possible in the entry point so startup-phase exceptions are captured.
|
|
101
|
+
"""
|
|
102
|
+
global _original_excepthook
|
|
103
|
+
if sys.excepthook is _excepthook:
|
|
104
|
+
return
|
|
105
|
+
_original_excepthook = sys.excepthook
|
|
106
|
+
sys.excepthook = _excepthook
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
# asyncio exception handler
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
_original_asyncio_handler: Any = None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _asyncio_handler(
|
|
117
|
+
loop: asyncio.AbstractEventLoop,
|
|
118
|
+
context: dict[str, Any],
|
|
119
|
+
) -> None:
|
|
120
|
+
exc = context.get("exception")
|
|
121
|
+
# CancelledError during shutdown/cancellation is normal control flow.
|
|
122
|
+
if exc is not None and not isinstance(exc, asyncio.CancelledError):
|
|
123
|
+
try:
|
|
124
|
+
from pythinker_code.telemetry import sentry as _sentry
|
|
125
|
+
from pythinker_code.telemetry import track
|
|
126
|
+
|
|
127
|
+
track(
|
|
128
|
+
"crash",
|
|
129
|
+
error_type=type(exc).__name__,
|
|
130
|
+
where=_phase,
|
|
131
|
+
source="asyncio_task",
|
|
132
|
+
)
|
|
133
|
+
_sentry.capture_exception(exc)
|
|
134
|
+
except Exception:
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
# Delegate so the original logging behavior (or custom handler) runs.
|
|
138
|
+
if _original_asyncio_handler is not None:
|
|
139
|
+
_original_asyncio_handler(loop, context)
|
|
140
|
+
else:
|
|
141
|
+
loop.default_exception_handler(context)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def install_asyncio_handler(loop: asyncio.AbstractEventLoop | None = None) -> None:
|
|
145
|
+
"""Install the crash handler on the given (or current running) loop.
|
|
146
|
+
|
|
147
|
+
Idempotent on the same loop. If a custom handler was already installed,
|
|
148
|
+
it is preserved and still invoked after the crash event is recorded.
|
|
149
|
+
"""
|
|
150
|
+
global _original_asyncio_handler
|
|
151
|
+
if loop is None:
|
|
152
|
+
loop = asyncio.get_running_loop()
|
|
153
|
+
current = loop.get_exception_handler()
|
|
154
|
+
if current is _asyncio_handler:
|
|
155
|
+
return
|
|
156
|
+
_original_asyncio_handler = current
|
|
157
|
+
loop.set_exception_handler(_asyncio_handler)
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Pre-bound OTel metric instruments for the agent loop.
|
|
2
|
+
|
|
3
|
+
Call sites stay declarative — they import the instruments and call ``.add()``
|
|
4
|
+
or ``.record()``. When telemetry is disabled, the instruments are still
|
|
5
|
+
present but back onto the no-op meter, so the cost is one attribute lookup
|
|
6
|
+
plus a no-op call.
|
|
7
|
+
|
|
8
|
+
Initialization happens automatically: ``otel.init()`` calls ``bind()`` once
|
|
9
|
+
the SDK MeterProvider is up. Until then, everything points at the global
|
|
10
|
+
no-op meter so unit tests and pre-init code paths stay safe.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from opentelemetry import metrics as _metrics
|
|
18
|
+
from opentelemetry.metrics import Counter, Histogram, Meter
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# Module-level instrument handles
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
# Initialized lazily on first ``bind()``. Until then, the global no-op meter
|
|
24
|
+
# returns no-op instruments — accessing them is safe.
|
|
25
|
+
|
|
26
|
+
_meter: Meter = _metrics.get_meter("pythinker-code")
|
|
27
|
+
|
|
28
|
+
# --- Turn-level ---
|
|
29
|
+
turn_total: Counter = _meter.create_counter(
|
|
30
|
+
"pythinker.turn.total",
|
|
31
|
+
description="Number of agent turns completed (one user message → final response).",
|
|
32
|
+
unit="1",
|
|
33
|
+
)
|
|
34
|
+
turn_duration_seconds: Histogram = _meter.create_histogram(
|
|
35
|
+
"pythinker.turn.duration_seconds",
|
|
36
|
+
description="End-to-end agent-turn duration.",
|
|
37
|
+
unit="s",
|
|
38
|
+
)
|
|
39
|
+
turn_step_count: Histogram = _meter.create_histogram(
|
|
40
|
+
"pythinker.turn.step_count",
|
|
41
|
+
description="Number of inner steps (LLM calls + tool loops) per turn.",
|
|
42
|
+
unit="1",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# --- LLM-level ---
|
|
46
|
+
llm_calls_total: Counter = _meter.create_counter(
|
|
47
|
+
"pythinker.llm.calls_total",
|
|
48
|
+
description="Number of LLM API calls.",
|
|
49
|
+
unit="1",
|
|
50
|
+
)
|
|
51
|
+
llm_duration_seconds: Histogram = _meter.create_histogram(
|
|
52
|
+
"pythinker.llm.duration_seconds",
|
|
53
|
+
description="LLM request duration.",
|
|
54
|
+
unit="s",
|
|
55
|
+
)
|
|
56
|
+
llm_input_tokens: Counter = _meter.create_counter(
|
|
57
|
+
"pythinker.llm.input_tokens",
|
|
58
|
+
description="Input/prompt tokens consumed (diagnostic — pythinker is BYO-key).",
|
|
59
|
+
unit="1",
|
|
60
|
+
)
|
|
61
|
+
llm_output_tokens: Counter = _meter.create_counter(
|
|
62
|
+
"pythinker.llm.output_tokens",
|
|
63
|
+
description="Output/completion tokens generated (diagnostic).",
|
|
64
|
+
unit="1",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# --- Tool-level ---
|
|
68
|
+
tool_calls_total: Counter = _meter.create_counter(
|
|
69
|
+
"pythinker.tool.calls_total",
|
|
70
|
+
description="Number of tool invocations (Read, Bash, Edit, MCP, …).",
|
|
71
|
+
unit="1",
|
|
72
|
+
)
|
|
73
|
+
tool_duration_seconds: Histogram = _meter.create_histogram(
|
|
74
|
+
"pythinker.tool.duration_seconds",
|
|
75
|
+
description="Tool execution duration.",
|
|
76
|
+
unit="s",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# --- Errors ---
|
|
80
|
+
errors_total: Counter = _meter.create_counter(
|
|
81
|
+
"pythinker.errors_total",
|
|
82
|
+
description="Errors observed by kind (api_error, tool_error, crash, …).",
|
|
83
|
+
unit="1",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def bind(meter: Meter) -> None:
|
|
88
|
+
"""Re-bind every instrument to a real (SDK) meter once OTel is initialized.
|
|
89
|
+
|
|
90
|
+
Called once from ``otel.init`` after the MeterProvider is up. Subsequent
|
|
91
|
+
calls are idempotent — but we replace the module-level handles so existing
|
|
92
|
+
references stay correct (Python rebinds names, not objects).
|
|
93
|
+
"""
|
|
94
|
+
global _meter
|
|
95
|
+
global turn_total, turn_duration_seconds, turn_step_count
|
|
96
|
+
global llm_calls_total, llm_duration_seconds, llm_input_tokens, llm_output_tokens
|
|
97
|
+
global tool_calls_total, tool_duration_seconds, errors_total
|
|
98
|
+
|
|
99
|
+
_meter = meter
|
|
100
|
+
turn_total = meter.create_counter(
|
|
101
|
+
"pythinker.turn.total",
|
|
102
|
+
description="Number of agent turns completed (one user message → final response).",
|
|
103
|
+
unit="1",
|
|
104
|
+
)
|
|
105
|
+
turn_duration_seconds = meter.create_histogram(
|
|
106
|
+
"pythinker.turn.duration_seconds",
|
|
107
|
+
description="End-to-end agent-turn duration.",
|
|
108
|
+
unit="s",
|
|
109
|
+
)
|
|
110
|
+
turn_step_count = meter.create_histogram(
|
|
111
|
+
"pythinker.turn.step_count",
|
|
112
|
+
description="Number of inner steps (LLM calls + tool loops) per turn.",
|
|
113
|
+
unit="1",
|
|
114
|
+
)
|
|
115
|
+
llm_calls_total = meter.create_counter(
|
|
116
|
+
"pythinker.llm.calls_total",
|
|
117
|
+
description="Number of LLM API calls.",
|
|
118
|
+
unit="1",
|
|
119
|
+
)
|
|
120
|
+
llm_duration_seconds = meter.create_histogram(
|
|
121
|
+
"pythinker.llm.duration_seconds",
|
|
122
|
+
description="LLM request duration.",
|
|
123
|
+
unit="s",
|
|
124
|
+
)
|
|
125
|
+
llm_input_tokens = meter.create_counter(
|
|
126
|
+
"pythinker.llm.input_tokens",
|
|
127
|
+
description="Input/prompt tokens consumed (diagnostic — pythinker is BYO-key).",
|
|
128
|
+
unit="1",
|
|
129
|
+
)
|
|
130
|
+
llm_output_tokens = meter.create_counter(
|
|
131
|
+
"pythinker.llm.output_tokens",
|
|
132
|
+
description="Output/completion tokens generated (diagnostic).",
|
|
133
|
+
unit="1",
|
|
134
|
+
)
|
|
135
|
+
tool_calls_total = meter.create_counter(
|
|
136
|
+
"pythinker.tool.calls_total",
|
|
137
|
+
description="Number of tool invocations (Read, Bash, Edit, MCP, …).",
|
|
138
|
+
unit="1",
|
|
139
|
+
)
|
|
140
|
+
tool_duration_seconds = meter.create_histogram(
|
|
141
|
+
"pythinker.tool.duration_seconds",
|
|
142
|
+
description="Tool execution duration.",
|
|
143
|
+
unit="s",
|
|
144
|
+
)
|
|
145
|
+
errors_total = meter.create_counter(
|
|
146
|
+
"pythinker.errors_total",
|
|
147
|
+
description="Errors observed by kind (api_error, tool_error, crash, …).",
|
|
148
|
+
unit="1",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
# Recording helpers — used by the agent loop
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def record_turn(*, duration_seconds: float, step_count: int, stop_reason: str) -> None:
|
|
158
|
+
"""Record a completed agent turn."""
|
|
159
|
+
attrs: dict[str, Any] = {"stop_reason": stop_reason}
|
|
160
|
+
turn_total.add(1, attrs)
|
|
161
|
+
turn_duration_seconds.record(duration_seconds, attrs)
|
|
162
|
+
turn_step_count.record(step_count, attrs)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def record_llm_call(
|
|
166
|
+
*,
|
|
167
|
+
duration_seconds: float,
|
|
168
|
+
system: str,
|
|
169
|
+
model: str,
|
|
170
|
+
input_tokens: int | None = None,
|
|
171
|
+
output_tokens: int | None = None,
|
|
172
|
+
success: bool = True,
|
|
173
|
+
) -> None:
|
|
174
|
+
"""Record one LLM API call."""
|
|
175
|
+
attrs: dict[str, Any] = {
|
|
176
|
+
"gen_ai.system": system,
|
|
177
|
+
"gen_ai.request.model": model,
|
|
178
|
+
"success": success,
|
|
179
|
+
}
|
|
180
|
+
llm_calls_total.add(1, attrs)
|
|
181
|
+
llm_duration_seconds.record(duration_seconds, attrs)
|
|
182
|
+
if input_tokens is not None and input_tokens > 0:
|
|
183
|
+
llm_input_tokens.add(input_tokens, attrs)
|
|
184
|
+
if output_tokens is not None and output_tokens > 0:
|
|
185
|
+
llm_output_tokens.add(output_tokens, attrs)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def record_tool_call(
|
|
189
|
+
*,
|
|
190
|
+
tool_name: str,
|
|
191
|
+
duration_seconds: float,
|
|
192
|
+
success: bool,
|
|
193
|
+
error_type: str | None = None,
|
|
194
|
+
) -> None:
|
|
195
|
+
"""Record one tool invocation."""
|
|
196
|
+
attrs: dict[str, Any] = {"tool.name": tool_name, "success": success}
|
|
197
|
+
if error_type:
|
|
198
|
+
attrs["error_type"] = error_type
|
|
199
|
+
tool_calls_total.add(1, attrs)
|
|
200
|
+
tool_duration_seconds.record(duration_seconds, attrs)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def record_error(*, kind: str, error_type: str | None = None) -> None:
|
|
204
|
+
"""Record an error by kind (api_error, tool_error, crash, …)."""
|
|
205
|
+
attrs: dict[str, Any] = {"kind": kind}
|
|
206
|
+
if error_type:
|
|
207
|
+
attrs["error_type"] = error_type
|
|
208
|
+
errors_total.add(1, attrs)
|