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,225 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
6
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
7
|
+
|
|
8
|
+
import aiohttp
|
|
9
|
+
|
|
10
|
+
from pythinker_code.auth import OPENAI_API_PLATFORM_ID
|
|
11
|
+
from pythinker_code.ui.shell.usage_adapters.base import UsageReport, UsageRow
|
|
12
|
+
from pythinker_code.utils.aiohttp import new_client_session
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from pythinker_code.auth.oauth import OAuthManager
|
|
16
|
+
from pythinker_code.config import LLMProvider
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
OPENAI_BASE = "https://api.openai.com/v1"
|
|
20
|
+
ADMIN_PREFIX = "sk-admin-"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class OpenAIAdminAdapter:
|
|
24
|
+
platform_id = OPENAI_API_PLATFORM_ID
|
|
25
|
+
requires_admin_key = True
|
|
26
|
+
provider_label = "OpenAI API"
|
|
27
|
+
|
|
28
|
+
async def fetch(
|
|
29
|
+
self,
|
|
30
|
+
provider: LLMProvider,
|
|
31
|
+
oauth_mgr: OAuthManager,
|
|
32
|
+
) -> UsageReport:
|
|
33
|
+
del oauth_mgr
|
|
34
|
+
admin_key = select_admin_key(provider.api_key.get_secret_value())
|
|
35
|
+
if admin_key is None:
|
|
36
|
+
return UsageReport(
|
|
37
|
+
self.provider_label,
|
|
38
|
+
None,
|
|
39
|
+
[],
|
|
40
|
+
notes=[
|
|
41
|
+
"Set OPENAI_ADMIN_KEY (sk-admin-…) to see usage and cost data.",
|
|
42
|
+
"The OpenAI Admin API does not accept regular sk-proj-… keys.",
|
|
43
|
+
],
|
|
44
|
+
unit_hint="USD",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
start_time = int(time.time()) - 24 * 60 * 60
|
|
48
|
+
costs = await _safe_get(
|
|
49
|
+
f"{OPENAI_BASE}/organization/costs",
|
|
50
|
+
{"start_time": start_time, "limit": 1},
|
|
51
|
+
admin_key,
|
|
52
|
+
)
|
|
53
|
+
completions = await _safe_get(
|
|
54
|
+
f"{OPENAI_BASE}/organization/usage/completions",
|
|
55
|
+
{
|
|
56
|
+
"start_time": start_time,
|
|
57
|
+
"bucket_width": "1d",
|
|
58
|
+
"limit": 7,
|
|
59
|
+
"group_by": "model",
|
|
60
|
+
},
|
|
61
|
+
admin_key,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
cost_summary = parse_openai_costs(costs) if costs is not None else None
|
|
65
|
+
completion_rows = parse_openai_completions(completions) if completions is not None else []
|
|
66
|
+
|
|
67
|
+
notes: list[str] = []
|
|
68
|
+
if costs is None:
|
|
69
|
+
notes.append("OpenAI costs endpoint returned no usable data.")
|
|
70
|
+
elif not _has_bucket_results_shape(costs) or not _has_cost_results_shape(costs):
|
|
71
|
+
notes.append("OpenAI cost response had unexpected shape.")
|
|
72
|
+
if completions is None:
|
|
73
|
+
notes.append("OpenAI completions usage endpoint returned no usable data.")
|
|
74
|
+
elif not _has_bucket_results_shape(completions):
|
|
75
|
+
notes.append("OpenAI completions usage response had unexpected shape.")
|
|
76
|
+
|
|
77
|
+
return UsageReport(
|
|
78
|
+
self.provider_label,
|
|
79
|
+
cost_summary,
|
|
80
|
+
completion_rows,
|
|
81
|
+
notes=notes,
|
|
82
|
+
unit_hint="USD + tokens",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def select_admin_key(provider_api_key: str) -> str | None:
|
|
87
|
+
env_key = os.getenv("OPENAI_ADMIN_KEY")
|
|
88
|
+
if env_key:
|
|
89
|
+
return env_key
|
|
90
|
+
if provider_api_key.startswith(ADMIN_PREFIX):
|
|
91
|
+
return provider_api_key
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
async def _safe_get(
|
|
96
|
+
url: str,
|
|
97
|
+
params: Mapping[str, str | int],
|
|
98
|
+
api_key: str,
|
|
99
|
+
) -> Mapping[str, Any] | None:
|
|
100
|
+
try:
|
|
101
|
+
async with (
|
|
102
|
+
new_client_session() as session,
|
|
103
|
+
session.get(
|
|
104
|
+
url,
|
|
105
|
+
params=params,
|
|
106
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
|
107
|
+
timeout=aiohttp.ClientTimeout(total=5),
|
|
108
|
+
raise_for_status=True,
|
|
109
|
+
) as resp,
|
|
110
|
+
):
|
|
111
|
+
payload = await resp.json()
|
|
112
|
+
except (TimeoutError, aiohttp.ClientError):
|
|
113
|
+
return None
|
|
114
|
+
if not isinstance(payload, Mapping):
|
|
115
|
+
return None
|
|
116
|
+
return cast(Mapping[str, Any], payload)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def parse_openai_costs(payload: Mapping[str, Any]) -> UsageRow | None:
|
|
120
|
+
data = _as_sequence(payload.get("data"))
|
|
121
|
+
if data is None:
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
total_cents = 0
|
|
125
|
+
for bucket_raw in data:
|
|
126
|
+
if not isinstance(bucket_raw, Mapping):
|
|
127
|
+
return None
|
|
128
|
+
bucket = cast(Mapping[str, Any], bucket_raw)
|
|
129
|
+
results = _as_sequence(bucket.get("results"))
|
|
130
|
+
if results is None:
|
|
131
|
+
return None
|
|
132
|
+
for result_raw in results:
|
|
133
|
+
if not isinstance(result_raw, Mapping):
|
|
134
|
+
return None
|
|
135
|
+
result = cast(Mapping[str, Any], result_raw)
|
|
136
|
+
amount = result.get("amount")
|
|
137
|
+
if not isinstance(amount, Mapping):
|
|
138
|
+
return None
|
|
139
|
+
cents = _to_cents(cast(Mapping[str, Any], amount).get("value"))
|
|
140
|
+
if cents is None:
|
|
141
|
+
return None
|
|
142
|
+
total_cents += cents
|
|
143
|
+
|
|
144
|
+
return UsageRow("Cost (last 24h)", total_cents, 0, unit="USD")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def parse_openai_completions(payload: Mapping[str, Any]) -> list[UsageRow]:
|
|
148
|
+
data = _as_sequence(payload.get("data"))
|
|
149
|
+
if data is None:
|
|
150
|
+
return []
|
|
151
|
+
|
|
152
|
+
totals: dict[str, int] = {}
|
|
153
|
+
for bucket_raw in data:
|
|
154
|
+
if not isinstance(bucket_raw, Mapping):
|
|
155
|
+
continue
|
|
156
|
+
bucket = cast(Mapping[str, Any], bucket_raw)
|
|
157
|
+
results = _as_sequence(bucket.get("results"))
|
|
158
|
+
if results is None:
|
|
159
|
+
continue
|
|
160
|
+
for result_raw in results:
|
|
161
|
+
if not isinstance(result_raw, Mapping):
|
|
162
|
+
continue
|
|
163
|
+
result = cast(Mapping[str, Any], result_raw)
|
|
164
|
+
model = result.get("model")
|
|
165
|
+
if not isinstance(model, str) or not model:
|
|
166
|
+
continue
|
|
167
|
+
totals[model] = totals.get(model, 0) + _to_int(result.get("input_tokens"))
|
|
168
|
+
totals[model] += _to_int(result.get("output_tokens"))
|
|
169
|
+
|
|
170
|
+
return [
|
|
171
|
+
UsageRow(model, used, 0, unit="tokens")
|
|
172
|
+
for model, used in sorted(totals.items(), key=lambda item: item[1], reverse=True)
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _as_sequence(value: Any) -> Sequence[Any] | None:
|
|
177
|
+
if isinstance(value, Sequence) and not isinstance(value, str | bytes | bytearray):
|
|
178
|
+
return cast(Sequence[Any], value)
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _has_bucket_results_shape(payload: Mapping[str, Any]) -> bool:
|
|
183
|
+
data = _as_sequence(payload.get("data"))
|
|
184
|
+
if data is None:
|
|
185
|
+
return False
|
|
186
|
+
for bucket_raw in data:
|
|
187
|
+
if not isinstance(bucket_raw, Mapping):
|
|
188
|
+
return False
|
|
189
|
+
bucket = cast(Mapping[str, Any], bucket_raw)
|
|
190
|
+
if _as_sequence(bucket.get("results")) is None:
|
|
191
|
+
return False
|
|
192
|
+
return True
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _has_cost_results_shape(payload: Mapping[str, Any]) -> bool:
|
|
196
|
+
data = _as_sequence(payload.get("data"))
|
|
197
|
+
if data is None:
|
|
198
|
+
return False
|
|
199
|
+
for bucket_raw in data:
|
|
200
|
+
bucket = cast(Mapping[str, Any], bucket_raw)
|
|
201
|
+
results = cast(Sequence[Any], bucket.get("results"))
|
|
202
|
+
for result_raw in results:
|
|
203
|
+
if not isinstance(result_raw, Mapping):
|
|
204
|
+
return False
|
|
205
|
+
result = cast(Mapping[str, Any], result_raw)
|
|
206
|
+
amount = result.get("amount")
|
|
207
|
+
if not isinstance(amount, Mapping):
|
|
208
|
+
return False
|
|
209
|
+
if _to_cents(cast(Mapping[str, Any], amount).get("value")) is None:
|
|
210
|
+
return False
|
|
211
|
+
return True
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _to_cents(value: Any) -> int | None:
|
|
215
|
+
try:
|
|
216
|
+
return int(round(float(value) * 100))
|
|
217
|
+
except (OverflowError, TypeError, ValueError):
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _to_int(value: Any) -> int:
|
|
222
|
+
try:
|
|
223
|
+
return int(value)
|
|
224
|
+
except (OverflowError, TypeError, ValueError):
|
|
225
|
+
return 0
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from datetime import UTC, datetime
|
|
5
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
6
|
+
|
|
7
|
+
import aiohttp
|
|
8
|
+
|
|
9
|
+
from pythinker_code.auth import OPENAI_CHATGPT_PLATFORM_ID
|
|
10
|
+
from pythinker_code.ui.shell.usage_adapters.base import UsageReport, UsageRow
|
|
11
|
+
from pythinker_code.utils.aiohttp import new_client_session
|
|
12
|
+
from pythinker_code.utils.datetime import format_duration
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from pythinker_code.auth.oauth import OAuthManager
|
|
16
|
+
from pythinker_code.config import LLMProvider
|
|
17
|
+
|
|
18
|
+
CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class OpenAIChatGPTAdapter:
|
|
22
|
+
platform_id = OPENAI_CHATGPT_PLATFORM_ID
|
|
23
|
+
requires_admin_key = False
|
|
24
|
+
provider_label = "ChatGPT Codex"
|
|
25
|
+
|
|
26
|
+
async def fetch(
|
|
27
|
+
self,
|
|
28
|
+
provider: LLMProvider,
|
|
29
|
+
oauth_mgr: OAuthManager,
|
|
30
|
+
) -> UsageReport:
|
|
31
|
+
if provider.oauth is None:
|
|
32
|
+
return UsageReport(
|
|
33
|
+
self.provider_label,
|
|
34
|
+
None,
|
|
35
|
+
[],
|
|
36
|
+
notes=["Codex usage requires an OAuth login (`/login openai-chatgpt`)."],
|
|
37
|
+
unit_hint="quota",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
access_token = oauth_mgr.resolve_api_key(provider.api_key, provider.oauth)
|
|
41
|
+
account_id = oauth_mgr.get_chatgpt_account_id(provider.oauth)
|
|
42
|
+
if not account_id:
|
|
43
|
+
return UsageReport(
|
|
44
|
+
self.provider_label,
|
|
45
|
+
None,
|
|
46
|
+
[],
|
|
47
|
+
notes=["Missing ChatGPT account_id in OAuth token."],
|
|
48
|
+
unit_hint="quota",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
headers = {
|
|
52
|
+
"Authorization": f"Bearer {access_token}",
|
|
53
|
+
"Accept": "application/json",
|
|
54
|
+
"ChatGPT-Account-Id": account_id,
|
|
55
|
+
"Origin": "https://chatgpt.com",
|
|
56
|
+
"Referer": "https://chatgpt.com/",
|
|
57
|
+
"User-Agent": "pythinker-code",
|
|
58
|
+
}
|
|
59
|
+
try:
|
|
60
|
+
async with (
|
|
61
|
+
new_client_session() as session,
|
|
62
|
+
session.get(
|
|
63
|
+
CODEX_USAGE_URL,
|
|
64
|
+
headers=headers,
|
|
65
|
+
timeout=aiohttp.ClientTimeout(total=5),
|
|
66
|
+
raise_for_status=True,
|
|
67
|
+
) as resp,
|
|
68
|
+
):
|
|
69
|
+
payload = await resp.json(content_type=None)
|
|
70
|
+
except aiohttp.ClientResponseError as e:
|
|
71
|
+
return UsageReport(
|
|
72
|
+
self.provider_label,
|
|
73
|
+
None,
|
|
74
|
+
[],
|
|
75
|
+
notes=[f"Codex usage fetch failed: HTTP {e.status}"],
|
|
76
|
+
unit_hint="quota",
|
|
77
|
+
)
|
|
78
|
+
except (aiohttp.ClientError, TimeoutError) as e:
|
|
79
|
+
return UsageReport(
|
|
80
|
+
self.provider_label,
|
|
81
|
+
None,
|
|
82
|
+
[],
|
|
83
|
+
notes=[f"Codex usage fetch failed: {e}"],
|
|
84
|
+
unit_hint="quota",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
return parse_codex_usage_payload(payload)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def parse_codex_usage_payload(payload: object) -> UsageReport:
|
|
91
|
+
if not isinstance(payload, Mapping):
|
|
92
|
+
return UsageReport(
|
|
93
|
+
OpenAIChatGPTAdapter.provider_label,
|
|
94
|
+
None,
|
|
95
|
+
[],
|
|
96
|
+
notes=["Unexpected Codex response shape."],
|
|
97
|
+
unit_hint="quota",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
payload_map = cast(Mapping[str, Any], payload)
|
|
101
|
+
rate_limit = payload_map.get("rate_limit") or payload_map.get("rate_limits")
|
|
102
|
+
if not isinstance(rate_limit, Mapping):
|
|
103
|
+
return UsageReport(
|
|
104
|
+
OpenAIChatGPTAdapter.provider_label,
|
|
105
|
+
None,
|
|
106
|
+
[],
|
|
107
|
+
notes=["No rate_limit data in Codex response."],
|
|
108
|
+
unit_hint="quota",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
rate_map = cast(Mapping[str, Any], rate_limit)
|
|
112
|
+
notes: list[str] = []
|
|
113
|
+
|
|
114
|
+
# `allowed` / `limit_reached` are top-level booleans on the current shape.
|
|
115
|
+
if rate_map.get("limit_reached") is True:
|
|
116
|
+
notes.append("Rate limit reached.")
|
|
117
|
+
elif rate_map.get("allowed") is False:
|
|
118
|
+
notes.append("Requests currently not allowed by the rate limiter.")
|
|
119
|
+
|
|
120
|
+
# Codex's wham/usage response carries up to two windows under
|
|
121
|
+
# `primary_window` / `secondary_window` (older releases used `five_hour` /
|
|
122
|
+
# `weekly`). Accounts on some plans only return one of them — and the slot
|
|
123
|
+
# name is NOT a reliable indicator of which window is shorter (e.g. free
|
|
124
|
+
# accounts can put a 7-day window in `primary_window`). Classify by
|
|
125
|
+
# `limit_window_seconds` instead, then sort smallest first.
|
|
126
|
+
rows: list[tuple[int, UsageRow]] = []
|
|
127
|
+
for slot in ("primary_window", "secondary_window", "five_hour", "weekly"):
|
|
128
|
+
window = rate_map.get(slot)
|
|
129
|
+
if not isinstance(window, Mapping):
|
|
130
|
+
continue
|
|
131
|
+
if pair := _row_for_codex_window(cast(Mapping[str, Any], window)):
|
|
132
|
+
rows.append(pair)
|
|
133
|
+
|
|
134
|
+
rows.sort(key=lambda item: item[0] or 10**12)
|
|
135
|
+
summary = rows[0][1] if rows else None
|
|
136
|
+
limits = [row for _, row in rows[1:]]
|
|
137
|
+
|
|
138
|
+
if summary is None and not limits:
|
|
139
|
+
# Surface what we did see so a future rename is obvious.
|
|
140
|
+
outer = ", ".join(sorted(rate_map.keys())) or "<empty>"
|
|
141
|
+
inner_hint = ""
|
|
142
|
+
for slot in ("primary_window", "secondary_window", "five_hour", "weekly"):
|
|
143
|
+
window = rate_map.get(slot)
|
|
144
|
+
if isinstance(window, Mapping):
|
|
145
|
+
inner_keys = ", ".join(sorted(cast(Mapping[str, Any], window).keys()))
|
|
146
|
+
inner_hint = f" Inner `{slot}` keys: {inner_keys}."
|
|
147
|
+
break
|
|
148
|
+
notes.append(
|
|
149
|
+
f"Codex returned rate_limit with no recognized quota fields "
|
|
150
|
+
f"(outer keys: {outer}).{inner_hint} "
|
|
151
|
+
f"Adapter expects a window object with `used_percent` (or "
|
|
152
|
+
f"`percent_left`) and `limit_window_seconds`."
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
return UsageReport(
|
|
156
|
+
OpenAIChatGPTAdapter.provider_label,
|
|
157
|
+
summary,
|
|
158
|
+
limits,
|
|
159
|
+
notes=notes,
|
|
160
|
+
unit_hint="quota",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _row_for_codex_window(window: Mapping[str, Any]) -> tuple[int, UsageRow] | None:
|
|
165
|
+
"""Build a UsageRow from a single Codex rate-limit window object.
|
|
166
|
+
|
|
167
|
+
Returns (window_seconds, row) so the caller can sort windows shortest-
|
|
168
|
+
first. Handles both the current snake_case shape (`used_percent`,
|
|
169
|
+
`limit_window_seconds`, `resets_at`) and the older shape (`percent_left`,
|
|
170
|
+
`limit_window_seconds`, `reset_at`).
|
|
171
|
+
"""
|
|
172
|
+
if "used_percent" in window:
|
|
173
|
+
used_percent = window.get("used_percent")
|
|
174
|
+
if not isinstance(used_percent, int | float):
|
|
175
|
+
return None
|
|
176
|
+
percent_left = max(0, 100 - int(used_percent))
|
|
177
|
+
elif "percent_left" in window:
|
|
178
|
+
raw_percent_left = window.get("percent_left")
|
|
179
|
+
if not isinstance(raw_percent_left, int | float):
|
|
180
|
+
return None
|
|
181
|
+
percent_left = int(raw_percent_left)
|
|
182
|
+
else:
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
raw_window_seconds = window.get("limit_window_seconds")
|
|
186
|
+
window_seconds = int(raw_window_seconds) if isinstance(raw_window_seconds, int | float) else 0
|
|
187
|
+
|
|
188
|
+
return window_seconds, UsageRow(
|
|
189
|
+
label=_label_for_codex_window(window_seconds),
|
|
190
|
+
used=percent_left,
|
|
191
|
+
limit=100,
|
|
192
|
+
unit="%",
|
|
193
|
+
reset_hint=_codex_reset_hint(window),
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _label_for_codex_window(seconds: int) -> str:
|
|
198
|
+
if seconds <= 0:
|
|
199
|
+
return "Rate limit"
|
|
200
|
+
if seconds <= 24 * 3600:
|
|
201
|
+
hours = seconds / 3600
|
|
202
|
+
return f"{hours:g}h window"
|
|
203
|
+
days = seconds // 86400
|
|
204
|
+
if 6 <= days <= 8:
|
|
205
|
+
return "Weekly window"
|
|
206
|
+
return f"{days}d window"
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _codex_reset_hint(win_map: Mapping[str, Any]) -> str | None:
|
|
210
|
+
# Current shape: `resets_at` is a unix-seconds timestamp.
|
|
211
|
+
# Older shape: `reset_at` is an ISO-8601 string.
|
|
212
|
+
resets_at_unix = win_map.get("resets_at")
|
|
213
|
+
if isinstance(resets_at_unix, int | float):
|
|
214
|
+
try:
|
|
215
|
+
dt = datetime.fromtimestamp(float(resets_at_unix), tz=UTC)
|
|
216
|
+
except (OverflowError, OSError, ValueError):
|
|
217
|
+
return None
|
|
218
|
+
return _format_reset_delta(dt, win_map)
|
|
219
|
+
|
|
220
|
+
reset_at = win_map.get("reset_at")
|
|
221
|
+
if reset_at is None:
|
|
222
|
+
return None
|
|
223
|
+
reset_at_str = str(reset_at)
|
|
224
|
+
try:
|
|
225
|
+
dt = datetime.fromisoformat(reset_at_str.replace("Z", "+00:00"))
|
|
226
|
+
except (TypeError, ValueError):
|
|
227
|
+
return f"resets at {reset_at_str}"
|
|
228
|
+
return _format_reset_delta(dt, win_map)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _format_reset_delta(dt: datetime, win_map: Mapping[str, Any]) -> str:
|
|
232
|
+
delta = dt - datetime.now(UTC)
|
|
233
|
+
seconds = int(delta.total_seconds())
|
|
234
|
+
if seconds <= 0:
|
|
235
|
+
window_seconds = win_map.get("limit_window_seconds")
|
|
236
|
+
if isinstance(window_seconds, int | float) and window_seconds > 0:
|
|
237
|
+
while seconds <= 0:
|
|
238
|
+
seconds += int(window_seconds)
|
|
239
|
+
if seconds <= 0:
|
|
240
|
+
return "reset"
|
|
241
|
+
return f"resets in {format_duration(seconds)}"
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""OpenCode Go usage adapter.
|
|
2
|
+
|
|
3
|
+
OpenCode Go has no public Bearer-API-key usage endpoint as of 2026-05-06
|
|
4
|
+
(verified via context7 `/anomalyco/opencode` and tavily — only chat
|
|
5
|
+
endpoints are documented; the upstream FR `anomalyco/opencode#16017` is
|
|
6
|
+
still open). Live consumption is only available by scraping the
|
|
7
|
+
authenticated workspace dashboard, which the
|
|
8
|
+
`slkiser/opencode-quota` plugin demonstrated:
|
|
9
|
+
|
|
10
|
+
GET https://opencode.ai/workspace/<workspace_id>/go
|
|
11
|
+
Cookie: auth=<auth_cookie>
|
|
12
|
+
|
|
13
|
+
The page is server-rendered with SolidJS and inlines a
|
|
14
|
+
`{rollingUsage,weeklyUsage,monthlyUsage}: $R[N]={ usagePercent, resetInSec }`
|
|
15
|
+
hydration block we can regex out. We mirror the slkiser parser shape so
|
|
16
|
+
the same workspace_id + auth_cookie config works in both tools.
|
|
17
|
+
|
|
18
|
+
If `OPENCODE_GO_WORKSPACE_ID` and `OPENCODE_GO_AUTH_COOKIE` are set the
|
|
19
|
+
adapter scrapes the live dashboard. Otherwise it falls back to the
|
|
20
|
+
published static plan caps ($12 / 5h, $30 / week, $60 / month per
|
|
21
|
+
opencode.ai/docs/go/) so the panel still has useful numbers.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import os
|
|
27
|
+
import re
|
|
28
|
+
from datetime import UTC, datetime, timedelta
|
|
29
|
+
from typing import TYPE_CHECKING
|
|
30
|
+
from urllib.parse import quote
|
|
31
|
+
|
|
32
|
+
import aiohttp
|
|
33
|
+
|
|
34
|
+
from pythinker_code.auth import OPENCODE_GO_PLATFORM_ID
|
|
35
|
+
from pythinker_code.ui.shell.usage_adapters.base import UsageReport, UsageRow
|
|
36
|
+
from pythinker_code.utils.aiohttp import new_client_session
|
|
37
|
+
from pythinker_code.utils.datetime import format_duration
|
|
38
|
+
|
|
39
|
+
if TYPE_CHECKING:
|
|
40
|
+
from pythinker_code.auth.oauth import OAuthManager
|
|
41
|
+
from pythinker_code.config import LLMProvider
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
_DASHBOARD_URL_PREFIX = "https://opencode.ai/workspace/"
|
|
45
|
+
_DASHBOARD_URL_SUFFIX = "/go"
|
|
46
|
+
_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Gecko/20100101 Firefox/148.0"
|
|
47
|
+
_SCRAPE_TIMEOUT_S = 10.0
|
|
48
|
+
|
|
49
|
+
_NUM = r"(-?\d+(?:\.\d+)?)"
|
|
50
|
+
_WINDOWS = (
|
|
51
|
+
("rollingUsage", "5h"),
|
|
52
|
+
("weeklyUsage", "Weekly"),
|
|
53
|
+
("monthlyUsage", "Monthly"),
|
|
54
|
+
)
|
|
55
|
+
# For each window, a pair of regexes (pct-first then reset-first) since
|
|
56
|
+
# SolidJS may emit fields in either order.
|
|
57
|
+
_WINDOW_REGEXES: dict[str, tuple[re.Pattern[str], re.Pattern[str]]] = {
|
|
58
|
+
name: (
|
|
59
|
+
re.compile(
|
|
60
|
+
rf"{name}:\$R\[\d+\]=\{{[^}}]*usagePercent:{_NUM}[^}}]*resetInSec:{_NUM}[^}}]*\}}"
|
|
61
|
+
),
|
|
62
|
+
re.compile(
|
|
63
|
+
rf"{name}:\$R\[\d+\]=\{{[^}}]*resetInSec:{_NUM}[^}}]*usagePercent:{_NUM}[^}}]*\}}"
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
for name, _ in _WINDOWS
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Plan caps published at https://opencode.ai/docs/go/ (verified 2026-05-06).
|
|
70
|
+
# Values in USD minor units (cents).
|
|
71
|
+
_PLAN_CAPS: dict[str, int] = {
|
|
72
|
+
"5h": 1200,
|
|
73
|
+
"Weekly": 3000,
|
|
74
|
+
"Monthly": 6000,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class OpenCodeGoAdapter:
|
|
79
|
+
platform_id = OPENCODE_GO_PLATFORM_ID
|
|
80
|
+
requires_admin_key = False
|
|
81
|
+
provider_label = "OpenCode Go"
|
|
82
|
+
|
|
83
|
+
async def fetch(
|
|
84
|
+
self,
|
|
85
|
+
provider: LLMProvider,
|
|
86
|
+
oauth_mgr: OAuthManager,
|
|
87
|
+
) -> UsageReport:
|
|
88
|
+
workspace_id = (os.getenv("OPENCODE_GO_WORKSPACE_ID") or "").strip()
|
|
89
|
+
auth_cookie = (os.getenv("OPENCODE_GO_AUTH_COOKIE") or "").strip()
|
|
90
|
+
|
|
91
|
+
if workspace_id and auth_cookie:
|
|
92
|
+
scraped, error = await _scrape_dashboard(workspace_id, auth_cookie)
|
|
93
|
+
if scraped:
|
|
94
|
+
return _report_from_windows(scraped, live=True)
|
|
95
|
+
return _report_from_caps(error_note=error)
|
|
96
|
+
|
|
97
|
+
if workspace_id or auth_cookie:
|
|
98
|
+
missing = "OPENCODE_GO_AUTH_COOKIE" if workspace_id else "OPENCODE_GO_WORKSPACE_ID"
|
|
99
|
+
return _report_from_caps(
|
|
100
|
+
error_note=(
|
|
101
|
+
f"Live OpenCode Go usage needs both env vars; missing {missing}. "
|
|
102
|
+
"Static plan caps shown above."
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return _report_from_caps(
|
|
107
|
+
error_note=(
|
|
108
|
+
"Set OPENCODE_GO_WORKSPACE_ID and OPENCODE_GO_AUTH_COOKIE to scrape "
|
|
109
|
+
"live usage from your workspace dashboard. Both come from your "
|
|
110
|
+
"browser session at opencode.ai (workspace id from the URL, "
|
|
111
|
+
"auth cookie from devtools → Application → Cookies). Static plan "
|
|
112
|
+
"caps shown above in the meantime."
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
async def _scrape_dashboard(
|
|
118
|
+
workspace_id: str, auth_cookie: str
|
|
119
|
+
) -> tuple[dict[str, tuple[float, float]] | None, str | None]:
|
|
120
|
+
"""Fetch the OpenCode Go dashboard and parse out window usage.
|
|
121
|
+
|
|
122
|
+
Returns (windows, None) on success, where windows maps the window label
|
|
123
|
+
(`5h`/`Weekly`/`Monthly`) to (`usage_percent`, `reset_in_sec`).
|
|
124
|
+
Returns (None, error_message) on any failure. Never raises.
|
|
125
|
+
"""
|
|
126
|
+
url = f"{_DASHBOARD_URL_PREFIX}{quote(workspace_id, safe='')}{_DASHBOARD_URL_SUFFIX}"
|
|
127
|
+
headers = {
|
|
128
|
+
"User-Agent": _USER_AGENT,
|
|
129
|
+
"Accept": "text/html",
|
|
130
|
+
"Cookie": f"auth={auth_cookie}",
|
|
131
|
+
}
|
|
132
|
+
try:
|
|
133
|
+
async with (
|
|
134
|
+
new_client_session() as session,
|
|
135
|
+
session.get(
|
|
136
|
+
url,
|
|
137
|
+
headers=headers,
|
|
138
|
+
timeout=aiohttp.ClientTimeout(total=_SCRAPE_TIMEOUT_S),
|
|
139
|
+
raise_for_status=True,
|
|
140
|
+
) as resp,
|
|
141
|
+
):
|
|
142
|
+
html = await resp.text()
|
|
143
|
+
except aiohttp.ClientResponseError as e:
|
|
144
|
+
if e.status in (401, 403):
|
|
145
|
+
return None, (
|
|
146
|
+
"OpenCode Go dashboard returned "
|
|
147
|
+
f"HTTP {e.status} — refresh your auth cookie from opencode.ai."
|
|
148
|
+
)
|
|
149
|
+
return None, f"OpenCode Go dashboard returned HTTP {e.status}"
|
|
150
|
+
except (TimeoutError, aiohttp.ClientError) as e:
|
|
151
|
+
return None, f"OpenCode Go dashboard fetch failed: {e}"
|
|
152
|
+
|
|
153
|
+
windows: dict[str, tuple[float, float]] = {}
|
|
154
|
+
for field, label in _WINDOWS:
|
|
155
|
+
pct_first, reset_first = _WINDOW_REGEXES[field]
|
|
156
|
+
if match := pct_first.search(html):
|
|
157
|
+
usage_percent, reset_in_sec = float(match.group(1)), float(match.group(2))
|
|
158
|
+
elif match := reset_first.search(html):
|
|
159
|
+
reset_in_sec, usage_percent = float(match.group(1)), float(match.group(2))
|
|
160
|
+
else:
|
|
161
|
+
continue
|
|
162
|
+
windows[label] = (usage_percent, reset_in_sec)
|
|
163
|
+
|
|
164
|
+
if not windows:
|
|
165
|
+
return None, (
|
|
166
|
+
"OpenCode Go dashboard scrape returned no recognizable windows — the "
|
|
167
|
+
"dashboard markup may have changed."
|
|
168
|
+
)
|
|
169
|
+
return windows, None
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _report_from_windows(windows: dict[str, tuple[float, float]], *, live: bool) -> UsageReport:
|
|
173
|
+
rows: list[UsageRow] = []
|
|
174
|
+
for label in ("5h", "Weekly", "Monthly"):
|
|
175
|
+
if label not in windows:
|
|
176
|
+
continue
|
|
177
|
+
usage_percent, reset_in_sec = windows[label]
|
|
178
|
+
percent_left = max(0, min(100, int(round(100 - usage_percent))))
|
|
179
|
+
rows.append(
|
|
180
|
+
UsageRow(
|
|
181
|
+
label=f"{label} window",
|
|
182
|
+
used=percent_left,
|
|
183
|
+
limit=100,
|
|
184
|
+
unit="%",
|
|
185
|
+
reset_hint=_reset_hint(reset_in_sec),
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
notes: list[str] = []
|
|
189
|
+
if live:
|
|
190
|
+
notes.append(
|
|
191
|
+
"Live OpenCode Go usage scraped from the workspace dashboard. "
|
|
192
|
+
f"Plan caps: 5h ${_PLAN_CAPS['5h'] / 100:.0f}, "
|
|
193
|
+
f"weekly ${_PLAN_CAPS['Weekly'] / 100:.0f}, "
|
|
194
|
+
f"monthly ${_PLAN_CAPS['Monthly'] / 100:.0f}."
|
|
195
|
+
)
|
|
196
|
+
summary = rows[0] if rows else None
|
|
197
|
+
return UsageReport(
|
|
198
|
+
provider_label=OpenCodeGoAdapter.provider_label,
|
|
199
|
+
summary=summary,
|
|
200
|
+
limits=rows[1:],
|
|
201
|
+
notes=notes,
|
|
202
|
+
unit_hint="quota",
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _report_from_caps(*, error_note: str | None) -> UsageReport:
|
|
207
|
+
rows = [
|
|
208
|
+
UsageRow(label=f"{label} cap", used=0, limit=cents, unit="USD")
|
|
209
|
+
for label, cents in _PLAN_CAPS.items()
|
|
210
|
+
]
|
|
211
|
+
notes: list[str] = []
|
|
212
|
+
if error_note:
|
|
213
|
+
notes.append(error_note)
|
|
214
|
+
notes.append(
|
|
215
|
+
"Live consumption isn't exposed via API key (anomalyco/opencode#16017). "
|
|
216
|
+
"The web console at opencode.ai is the authoritative source."
|
|
217
|
+
)
|
|
218
|
+
return UsageReport(
|
|
219
|
+
provider_label=OpenCodeGoAdapter.provider_label,
|
|
220
|
+
summary=rows[0],
|
|
221
|
+
limits=rows[1:],
|
|
222
|
+
notes=notes,
|
|
223
|
+
unit_hint="quota",
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _reset_hint(reset_in_sec: float) -> str | None:
|
|
228
|
+
seconds = int(reset_in_sec)
|
|
229
|
+
if seconds <= 0:
|
|
230
|
+
return None
|
|
231
|
+
reset_at = datetime.now(UTC) + timedelta(seconds=seconds)
|
|
232
|
+
return f"resets in {format_duration(seconds)} ({reset_at.strftime('%Y-%m-%d %H:%M UTC')})"
|