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,192 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from collections.abc import AsyncIterator
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, cast
|
|
7
|
+
|
|
8
|
+
import aiohttp
|
|
9
|
+
from pydantic import SecretStr
|
|
10
|
+
|
|
11
|
+
from pythinker_code.auth import DEEPSEEK_PLATFORM_ID
|
|
12
|
+
from pythinker_code.auth.oauth import OAuthEvent
|
|
13
|
+
from pythinker_code.config import Config, LLMModel, LLMProvider, save_config
|
|
14
|
+
from pythinker_code.utils.aiohttp import new_client_session
|
|
15
|
+
|
|
16
|
+
DEEPSEEK_BASE_URL = "https://api.deepseek.com/v1"
|
|
17
|
+
DEEPSEEK_PROVIDER_KEY = "managed:deepseek"
|
|
18
|
+
DEEPSEEK_DEFAULT_MODEL_ALIAS = "deepseek/v4-pro"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True, slots=True)
|
|
22
|
+
class DeepSeekModel:
|
|
23
|
+
model_id: str
|
|
24
|
+
alias_suffix: str
|
|
25
|
+
display_name: str
|
|
26
|
+
provider_key: str = DEEPSEEK_PROVIDER_KEY
|
|
27
|
+
max_context_size: int = 128_000
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def alias(self) -> str:
|
|
31
|
+
return f"{DEEPSEEK_PLATFORM_ID}/{self.alias_suffix}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
DEEPSEEK_MODELS: tuple[DeepSeekModel, ...] = (
|
|
35
|
+
DeepSeekModel("deepseek-v4-pro", "v4-pro", "DeepSeek V4 Pro"),
|
|
36
|
+
DeepSeekModel("deepseek-v4-flash", "v4-flash", "DeepSeek V4 Flash"),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_deepseek_api_key_from_env() -> str | None:
|
|
41
|
+
value = os.getenv("DEEPSEEK_API_KEY")
|
|
42
|
+
if value and value.strip():
|
|
43
|
+
return value.strip()
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _apply_deepseek_config(
|
|
48
|
+
config: Config,
|
|
49
|
+
api_key: SecretStr,
|
|
50
|
+
models: tuple[DeepSeekModel, ...] = DEEPSEEK_MODELS,
|
|
51
|
+
) -> None:
|
|
52
|
+
config.providers[DEEPSEEK_PROVIDER_KEY] = LLMProvider(
|
|
53
|
+
type="openai_legacy",
|
|
54
|
+
base_url=DEEPSEEK_BASE_URL,
|
|
55
|
+
api_key=api_key,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
provider_keys = {DEEPSEEK_PROVIDER_KEY}
|
|
59
|
+
for key, model in list(config.models.items()):
|
|
60
|
+
if model.provider in provider_keys:
|
|
61
|
+
del config.models[key]
|
|
62
|
+
|
|
63
|
+
for model in models:
|
|
64
|
+
config.models[model.alias] = LLMModel(
|
|
65
|
+
provider=model.provider_key,
|
|
66
|
+
model=model.model_id,
|
|
67
|
+
max_context_size=model.max_context_size,
|
|
68
|
+
display_name=model.display_name,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
fallback = next(
|
|
72
|
+
(m.alias for m in models),
|
|
73
|
+
next(iter(config.models), ""),
|
|
74
|
+
)
|
|
75
|
+
if DEEPSEEK_DEFAULT_MODEL_ALIAS in config.models:
|
|
76
|
+
config.default_model = DEEPSEEK_DEFAULT_MODEL_ALIAS
|
|
77
|
+
else:
|
|
78
|
+
config.default_model = fallback
|
|
79
|
+
config.default_thinking = False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _model_by_id() -> dict[str, DeepSeekModel]:
|
|
83
|
+
return {model.model_id: model for model in DEEPSEEK_MODELS}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _parse_discovered_models(data: object) -> tuple[DeepSeekModel, ...]:
|
|
87
|
+
if not isinstance(data, dict):
|
|
88
|
+
return ()
|
|
89
|
+
data = cast(dict[str, Any], data)
|
|
90
|
+
raw_items = data.get("data")
|
|
91
|
+
if not isinstance(raw_items, list):
|
|
92
|
+
return ()
|
|
93
|
+
|
|
94
|
+
known = _model_by_id()
|
|
95
|
+
result: list[DeepSeekModel] = []
|
|
96
|
+
for item in cast(list[dict[str, Any]], raw_items):
|
|
97
|
+
model_id = item.get("id")
|
|
98
|
+
if not isinstance(model_id, str) or model_id not in known:
|
|
99
|
+
continue
|
|
100
|
+
current = known[model_id]
|
|
101
|
+
context_length = item.get("context_length")
|
|
102
|
+
max_context_size = current.max_context_size
|
|
103
|
+
if isinstance(context_length, int) and context_length > 0:
|
|
104
|
+
max_context_size = context_length
|
|
105
|
+
display_name_raw = item.get("display_name")
|
|
106
|
+
display_name = (
|
|
107
|
+
display_name_raw
|
|
108
|
+
if isinstance(display_name_raw, str) and display_name_raw
|
|
109
|
+
else current.display_name
|
|
110
|
+
)
|
|
111
|
+
result.append(
|
|
112
|
+
DeepSeekModel(
|
|
113
|
+
model_id=current.model_id,
|
|
114
|
+
alias_suffix=current.alias_suffix,
|
|
115
|
+
display_name=display_name,
|
|
116
|
+
provider_key=current.provider_key,
|
|
117
|
+
max_context_size=max_context_size,
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
return tuple(result)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
async def _discover_deepseek_models(api_key: str) -> tuple[DeepSeekModel, ...]:
|
|
124
|
+
async with (
|
|
125
|
+
new_client_session() as session,
|
|
126
|
+
session.get(
|
|
127
|
+
f"{DEEPSEEK_BASE_URL}/models",
|
|
128
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
|
129
|
+
raise_for_status=True,
|
|
130
|
+
) as response,
|
|
131
|
+
):
|
|
132
|
+
payload = await response.json(content_type=None)
|
|
133
|
+
return _parse_discovered_models(payload)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
async def login_deepseek_api_key(
|
|
137
|
+
config: Config, api_key: str | None = None
|
|
138
|
+
) -> AsyncIterator[OAuthEvent]:
|
|
139
|
+
if not config.is_from_default_location:
|
|
140
|
+
yield OAuthEvent(
|
|
141
|
+
"error",
|
|
142
|
+
"Login requires the default config file; restart without --config/--config-file.",
|
|
143
|
+
)
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
resolved_key = (api_key or get_deepseek_api_key_from_env() or "").strip()
|
|
147
|
+
if not resolved_key:
|
|
148
|
+
yield OAuthEvent("error", "DeepSeek API key is required.")
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
models = DEEPSEEK_MODELS
|
|
152
|
+
try:
|
|
153
|
+
discovered = await _discover_deepseek_models(resolved_key)
|
|
154
|
+
if discovered:
|
|
155
|
+
models = discovered
|
|
156
|
+
except aiohttp.ClientResponseError as exc:
|
|
157
|
+
if exc.status in {401, 403}:
|
|
158
|
+
yield OAuthEvent("error", "Invalid DeepSeek API key; the key was not saved.")
|
|
159
|
+
return
|
|
160
|
+
yield OAuthEvent(
|
|
161
|
+
"info",
|
|
162
|
+
"DeepSeek model listing is unavailable; using the built-in model list.",
|
|
163
|
+
)
|
|
164
|
+
except (aiohttp.ClientError, TimeoutError, ValueError):
|
|
165
|
+
yield OAuthEvent(
|
|
166
|
+
"info",
|
|
167
|
+
"DeepSeek model listing is unavailable; using the built-in model list.",
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
_apply_deepseek_config(config, SecretStr(resolved_key), models=models)
|
|
171
|
+
save_config(config)
|
|
172
|
+
yield OAuthEvent("success", f"DeepSeek configured with model {config.default_model}.")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
async def logout_deepseek(config: Config) -> AsyncIterator[OAuthEvent]:
|
|
176
|
+
if not config.is_from_default_location:
|
|
177
|
+
yield OAuthEvent(
|
|
178
|
+
"error",
|
|
179
|
+
"Logout requires the default config file; restart without --config/--config-file.",
|
|
180
|
+
)
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
provider_keys = {DEEPSEEK_PROVIDER_KEY}
|
|
184
|
+
config.providers.pop(DEEPSEEK_PROVIDER_KEY, None)
|
|
185
|
+
for key, model in list(config.models.items()):
|
|
186
|
+
if model.provider in provider_keys:
|
|
187
|
+
del config.models[key]
|
|
188
|
+
|
|
189
|
+
if config.default_model not in config.models:
|
|
190
|
+
config.default_model = next(iter(config.models), "")
|
|
191
|
+
save_config(config)
|
|
192
|
+
yield OAuthEvent("success", "Logged out of DeepSeek successfully.")
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from collections.abc import AsyncIterator
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, cast
|
|
7
|
+
|
|
8
|
+
import aiohttp
|
|
9
|
+
from pydantic import SecretStr
|
|
10
|
+
|
|
11
|
+
from pythinker_code.auth import LM_STUDIO_PLATFORM_ID
|
|
12
|
+
from pythinker_code.auth.oauth import OAuthEvent
|
|
13
|
+
from pythinker_code.auth.platforms import LOCAL_API_KEY_PLACEHOLDER, bearer_headers
|
|
14
|
+
from pythinker_code.config import Config, LLMModel, LLMProvider, save_config
|
|
15
|
+
from pythinker_code.utils.aiohttp import new_client_session
|
|
16
|
+
|
|
17
|
+
LM_STUDIO_BASE_URL = "http://localhost:1234/v1"
|
|
18
|
+
LM_STUDIO_PROVIDER_KEY = "managed:lm-studio"
|
|
19
|
+
LM_STUDIO_DEFAULT_CONTEXT_SIZE = 32768
|
|
20
|
+
# The agent's system prompt + tool schemas + skills already runs ~16k tokens
|
|
21
|
+
# before any user input. Anything below this on the LM Studio side will fail
|
|
22
|
+
# on the first chat with `n_keep:N >= n_ctx:M`.
|
|
23
|
+
LM_STUDIO_MIN_RECOMMENDED_LOADED_CONTEXT = 32768
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True, slots=True)
|
|
27
|
+
class LMStudioModel:
|
|
28
|
+
model_id: str
|
|
29
|
+
display_name: str
|
|
30
|
+
max_context_size: int = LM_STUDIO_DEFAULT_CONTEXT_SIZE
|
|
31
|
+
provider_key: str = LM_STUDIO_PROVIDER_KEY
|
|
32
|
+
# Native v0 metadata used for the smart context-length warning. Both are 0
|
|
33
|
+
# when discovery falls back to OpenAI-compat /v1/models.
|
|
34
|
+
state: str = "" # "loaded" | "not-loaded" | ""
|
|
35
|
+
max_context_length: int = 0 # the model's hard ceiling
|
|
36
|
+
loaded_context_length: int = 0 # what LM Studio actually loaded the model with
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def alias(self) -> str:
|
|
40
|
+
return f"{LM_STUDIO_PLATFORM_ID}/{self.model_id}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_lm_studio_api_key_from_env() -> str | None:
|
|
44
|
+
value = os.getenv("LM_STUDIO_API_KEY")
|
|
45
|
+
if value and value.strip():
|
|
46
|
+
return value.strip()
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_lm_studio_base_url_from_env() -> str | None:
|
|
51
|
+
value = os.getenv("LM_STUDIO_BASE_URL")
|
|
52
|
+
if value and value.strip():
|
|
53
|
+
return value.strip()
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _root_url(base_url: str) -> str:
|
|
58
|
+
"""Strip trailing /v1 (or /v1/) so we can hit /api/v0/* on the same host."""
|
|
59
|
+
trimmed = base_url.rstrip("/")
|
|
60
|
+
if trimmed.endswith("/v1"):
|
|
61
|
+
return trimmed[: -len("/v1")]
|
|
62
|
+
return trimmed
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def _discover_lm_studio_models(
|
|
66
|
+
base_url: str,
|
|
67
|
+
api_key: str,
|
|
68
|
+
) -> tuple[LMStudioModel, ...]:
|
|
69
|
+
"""Prefer the native v0 endpoint (richer metadata); fall back to /v1/models."""
|
|
70
|
+
headers = bearer_headers(api_key)
|
|
71
|
+
root = _root_url(base_url)
|
|
72
|
+
timeout = aiohttp.ClientTimeout(total=5)
|
|
73
|
+
|
|
74
|
+
async with new_client_session() as session:
|
|
75
|
+
# Path A: native enrichment.
|
|
76
|
+
try:
|
|
77
|
+
async with session.get(
|
|
78
|
+
f"{root}/api/v0/models",
|
|
79
|
+
headers=headers,
|
|
80
|
+
timeout=timeout,
|
|
81
|
+
raise_for_status=True,
|
|
82
|
+
) as response:
|
|
83
|
+
payload = await response.json(content_type=None)
|
|
84
|
+
return _parse_native_lm_studio_models(payload)
|
|
85
|
+
except (aiohttp.ClientResponseError, aiohttp.ClientError, TimeoutError):
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
# Path B: OpenAI-compat fallback.
|
|
89
|
+
async with session.get(
|
|
90
|
+
f"{base_url.rstrip('/')}/models",
|
|
91
|
+
headers=headers,
|
|
92
|
+
timeout=timeout,
|
|
93
|
+
raise_for_status=True,
|
|
94
|
+
) as response:
|
|
95
|
+
payload = await response.json(content_type=None)
|
|
96
|
+
return _parse_openai_compat_models(payload)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _parse_native_lm_studio_models(payload: object) -> tuple[LMStudioModel, ...]:
|
|
100
|
+
if not isinstance(payload, dict):
|
|
101
|
+
return ()
|
|
102
|
+
payload = cast(dict[str, Any], payload)
|
|
103
|
+
raw_items = payload.get("data")
|
|
104
|
+
if not isinstance(raw_items, list):
|
|
105
|
+
return ()
|
|
106
|
+
|
|
107
|
+
result: list[LMStudioModel] = []
|
|
108
|
+
for item in cast(list[dict[str, Any]], raw_items):
|
|
109
|
+
model_id = item.get("id")
|
|
110
|
+
if not isinstance(model_id, str):
|
|
111
|
+
continue
|
|
112
|
+
if item.get("type") not in (None, "llm", "vlm"):
|
|
113
|
+
# Skip embedding models; they are not chat-capable.
|
|
114
|
+
continue
|
|
115
|
+
max_ctx_raw = item.get("max_context_length")
|
|
116
|
+
max_ctx = max_ctx_raw if isinstance(max_ctx_raw, int) and max_ctx_raw > 0 else 0
|
|
117
|
+
loaded_ctx_raw = item.get("loaded_context_length")
|
|
118
|
+
loaded_ctx = loaded_ctx_raw if isinstance(loaded_ctx_raw, int) and loaded_ctx_raw > 0 else 0
|
|
119
|
+
state_raw = item.get("state")
|
|
120
|
+
state = state_raw if isinstance(state_raw, str) else ""
|
|
121
|
+
# Effective max_context_size for the agent:
|
|
122
|
+
# - if loaded → the actual loaded ceiling (what the model can really handle now)
|
|
123
|
+
# - if not-loaded → the model's max (will be capped by JIT-load default)
|
|
124
|
+
if state == "loaded" and loaded_ctx > 0:
|
|
125
|
+
effective_ctx = loaded_ctx
|
|
126
|
+
elif max_ctx > 0:
|
|
127
|
+
effective_ctx = max_ctx
|
|
128
|
+
else:
|
|
129
|
+
effective_ctx = LM_STUDIO_DEFAULT_CONTEXT_SIZE
|
|
130
|
+
arch = item.get("arch") if isinstance(item.get("arch"), str) else None
|
|
131
|
+
quant = item.get("quantization") if isinstance(item.get("quantization"), str) else None
|
|
132
|
+
display = (
|
|
133
|
+
" ".join(part for part in (arch, f"({quant})" if quant else None) if part) or model_id
|
|
134
|
+
)
|
|
135
|
+
result.append(
|
|
136
|
+
LMStudioModel(
|
|
137
|
+
model_id=model_id,
|
|
138
|
+
display_name=display,
|
|
139
|
+
max_context_size=effective_ctx,
|
|
140
|
+
state=state,
|
|
141
|
+
max_context_length=max_ctx,
|
|
142
|
+
loaded_context_length=loaded_ctx,
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
return tuple(result)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _parse_openai_compat_models(payload: object) -> tuple[LMStudioModel, ...]:
|
|
149
|
+
if not isinstance(payload, dict):
|
|
150
|
+
return ()
|
|
151
|
+
payload = cast(dict[str, Any], payload)
|
|
152
|
+
raw_items = payload.get("data")
|
|
153
|
+
if not isinstance(raw_items, list):
|
|
154
|
+
return ()
|
|
155
|
+
result: list[LMStudioModel] = []
|
|
156
|
+
for item in cast(list[dict[str, Any]], raw_items):
|
|
157
|
+
model_id = item.get("id")
|
|
158
|
+
if not isinstance(model_id, str):
|
|
159
|
+
continue
|
|
160
|
+
result.append(
|
|
161
|
+
LMStudioModel(
|
|
162
|
+
model_id=model_id,
|
|
163
|
+
display_name=model_id,
|
|
164
|
+
max_context_size=LM_STUDIO_DEFAULT_CONTEXT_SIZE,
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
return tuple(result)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _apply_lm_studio_config(
|
|
171
|
+
config: Config,
|
|
172
|
+
api_key: SecretStr,
|
|
173
|
+
*,
|
|
174
|
+
base_url: str,
|
|
175
|
+
models: tuple[LMStudioModel, ...],
|
|
176
|
+
) -> None:
|
|
177
|
+
config.providers[LM_STUDIO_PROVIDER_KEY] = LLMProvider(
|
|
178
|
+
type="openai_legacy",
|
|
179
|
+
base_url=base_url,
|
|
180
|
+
api_key=api_key,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# Replace any prior LM Studio aliases.
|
|
184
|
+
for key, model in list(config.models.items()):
|
|
185
|
+
if model.provider == LM_STUDIO_PROVIDER_KEY:
|
|
186
|
+
del config.models[key]
|
|
187
|
+
|
|
188
|
+
for model in models:
|
|
189
|
+
config.models[model.alias] = LLMModel(
|
|
190
|
+
provider=model.provider_key,
|
|
191
|
+
model=model.model_id,
|
|
192
|
+
max_context_size=model.max_context_size,
|
|
193
|
+
display_name=model.display_name,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
if not models:
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
# Pick the model with the largest context, ties broken alphabetically (first wins).
|
|
200
|
+
best = min(models, key=lambda m: (-m.max_context_size, m.alias))
|
|
201
|
+
config.default_model = best.alias
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@dataclass(frozen=True, slots=True)
|
|
205
|
+
class LMStudioLoadResult:
|
|
206
|
+
model_id: str
|
|
207
|
+
status: str
|
|
208
|
+
load_time_seconds: float
|
|
209
|
+
instance_id: str | None = None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
async def _is_lm_studio_model_loaded(
|
|
213
|
+
session: aiohttp.ClientSession,
|
|
214
|
+
*,
|
|
215
|
+
root: str,
|
|
216
|
+
headers: dict[str, str],
|
|
217
|
+
model_id: str,
|
|
218
|
+
timeout: aiohttp.ClientTimeout,
|
|
219
|
+
) -> bool:
|
|
220
|
+
"""Check `state` on /api/v0/models; True if the model is already loaded."""
|
|
221
|
+
try:
|
|
222
|
+
async with session.get(
|
|
223
|
+
f"{root}/api/v0/models",
|
|
224
|
+
headers=headers,
|
|
225
|
+
timeout=timeout,
|
|
226
|
+
raise_for_status=True,
|
|
227
|
+
) as response:
|
|
228
|
+
payload: object = await response.json(content_type=None)
|
|
229
|
+
except (aiohttp.ClientError, TimeoutError):
|
|
230
|
+
return False
|
|
231
|
+
|
|
232
|
+
if not isinstance(payload, dict):
|
|
233
|
+
return False
|
|
234
|
+
payload = cast(dict[str, Any], payload)
|
|
235
|
+
raw_items = payload.get("data")
|
|
236
|
+
if not isinstance(raw_items, list):
|
|
237
|
+
return False
|
|
238
|
+
for item in cast(list[dict[str, Any]], raw_items):
|
|
239
|
+
if item.get("id") == model_id and item.get("state") == "loaded":
|
|
240
|
+
return True
|
|
241
|
+
return False
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
async def request_lm_studio_load(
|
|
245
|
+
*,
|
|
246
|
+
base_url: str,
|
|
247
|
+
model_id: str,
|
|
248
|
+
api_key: str,
|
|
249
|
+
timeout_s: float = 180.0,
|
|
250
|
+
) -> LMStudioLoadResult:
|
|
251
|
+
"""Ask LM Studio to load a model.
|
|
252
|
+
|
|
253
|
+
Checks /api/v0/models first; if the model is already `state=loaded`,
|
|
254
|
+
returns immediately without a redundant /api/v1/models/load call (which
|
|
255
|
+
would otherwise spawn an additional instance).
|
|
256
|
+
|
|
257
|
+
Long timeout because cold-loading a large model can take a while.
|
|
258
|
+
|
|
259
|
+
Raises aiohttp.ClientError on transport failure. On HTTP errors from the
|
|
260
|
+
load endpoint (e.g., VRAM exhaustion), reads the JSON error body and
|
|
261
|
+
raises RuntimeError with the actual message instead of a bare status code.
|
|
262
|
+
"""
|
|
263
|
+
headers = bearer_headers(api_key)
|
|
264
|
+
root = _root_url(base_url)
|
|
265
|
+
timeout = aiohttp.ClientTimeout(total=timeout_s)
|
|
266
|
+
|
|
267
|
+
async with new_client_session() as session:
|
|
268
|
+
if await _is_lm_studio_model_loaded(
|
|
269
|
+
session, root=root, headers=headers, model_id=model_id, timeout=timeout
|
|
270
|
+
):
|
|
271
|
+
return LMStudioLoadResult(
|
|
272
|
+
model_id=model_id, status="already-loaded", load_time_seconds=0.0
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
async with session.post(
|
|
276
|
+
f"{root}/api/v1/models/load",
|
|
277
|
+
json={"model": model_id},
|
|
278
|
+
headers=headers,
|
|
279
|
+
timeout=timeout,
|
|
280
|
+
) as response:
|
|
281
|
+
payload: object = await response.json(content_type=None)
|
|
282
|
+
if response.status >= 400:
|
|
283
|
+
msg = ""
|
|
284
|
+
if isinstance(payload, dict):
|
|
285
|
+
payload_dict = cast(dict[str, Any], payload)
|
|
286
|
+
err = payload_dict.get("error")
|
|
287
|
+
if isinstance(err, dict):
|
|
288
|
+
err = cast(dict[str, Any], err)
|
|
289
|
+
msg = str(err.get("message") or err.get("type") or "")
|
|
290
|
+
raise RuntimeError(
|
|
291
|
+
f"LM Studio refused to load {model_id} "
|
|
292
|
+
f"(HTTP {response.status}): {msg or 'no message'}"
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
if not isinstance(payload, dict):
|
|
296
|
+
raise ValueError(f"Unexpected /api/v1/models/load response: {payload!r}")
|
|
297
|
+
payload = cast(dict[str, Any], payload)
|
|
298
|
+
return LMStudioLoadResult(
|
|
299
|
+
model_id=model_id,
|
|
300
|
+
status=str(payload.get("status") or ""),
|
|
301
|
+
load_time_seconds=float(payload.get("load_time_seconds") or 0.0),
|
|
302
|
+
instance_id=(
|
|
303
|
+
str(payload["instance_id"]) if isinstance(payload.get("instance_id"), str) else None
|
|
304
|
+
),
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
async def login_lm_studio(
|
|
309
|
+
config: Config,
|
|
310
|
+
api_key: str | None = None,
|
|
311
|
+
base_url: str | None = None,
|
|
312
|
+
) -> AsyncIterator[OAuthEvent]:
|
|
313
|
+
if not config.is_from_default_location:
|
|
314
|
+
yield OAuthEvent(
|
|
315
|
+
"error",
|
|
316
|
+
"Login requires the default config file; restart without --config/--config-file.",
|
|
317
|
+
)
|
|
318
|
+
return
|
|
319
|
+
|
|
320
|
+
resolved_url = (base_url or get_lm_studio_base_url_from_env() or LM_STUDIO_BASE_URL).strip()
|
|
321
|
+
if not resolved_url:
|
|
322
|
+
yield OAuthEvent(
|
|
323
|
+
"error",
|
|
324
|
+
"LM Studio base URL is empty; pass a non-empty --base-url or set LM_STUDIO_BASE_URL.",
|
|
325
|
+
)
|
|
326
|
+
return
|
|
327
|
+
resolved_key = (
|
|
328
|
+
api_key or get_lm_studio_api_key_from_env() or LOCAL_API_KEY_PLACEHOLDER
|
|
329
|
+
).strip()
|
|
330
|
+
|
|
331
|
+
try:
|
|
332
|
+
models = await _discover_lm_studio_models(resolved_url, resolved_key)
|
|
333
|
+
except aiohttp.ClientResponseError as exc:
|
|
334
|
+
if exc.status in (401, 403):
|
|
335
|
+
yield OAuthEvent("error", "LM Studio rejected the API key; the key was not saved.")
|
|
336
|
+
return
|
|
337
|
+
yield OAuthEvent(
|
|
338
|
+
"error",
|
|
339
|
+
f"LM Studio model listing failed ({exc.status}); the provider was not saved.",
|
|
340
|
+
)
|
|
341
|
+
return
|
|
342
|
+
except (aiohttp.ClientError, TimeoutError, ConnectionError) as exc:
|
|
343
|
+
try:
|
|
344
|
+
detail = str(exc)
|
|
345
|
+
except Exception:
|
|
346
|
+
detail = type(exc).__name__
|
|
347
|
+
yield OAuthEvent(
|
|
348
|
+
"error",
|
|
349
|
+
f"LM Studio server is not reachable at {resolved_url}; start it and retry. ({detail})",
|
|
350
|
+
)
|
|
351
|
+
return
|
|
352
|
+
|
|
353
|
+
if not models:
|
|
354
|
+
yield OAuthEvent(
|
|
355
|
+
"error",
|
|
356
|
+
"LM Studio reported zero loaded chat models; "
|
|
357
|
+
"load a model in the LM Studio UI and retry.",
|
|
358
|
+
)
|
|
359
|
+
return
|
|
360
|
+
|
|
361
|
+
_apply_lm_studio_config(
|
|
362
|
+
config,
|
|
363
|
+
SecretStr(resolved_key),
|
|
364
|
+
base_url=resolved_url,
|
|
365
|
+
models=models,
|
|
366
|
+
)
|
|
367
|
+
save_config(config)
|
|
368
|
+
yield OAuthEvent(
|
|
369
|
+
"success",
|
|
370
|
+
f"LM Studio configured at {resolved_url} with {len(models)} model(s); "
|
|
371
|
+
f"default = {config.default_model}.",
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
# Smart context-length check: any currently-loaded model whose actual
|
|
375
|
+
# `loaded_context_length` is below what the agent's system prompt needs
|
|
376
|
+
# will fail on first chat. Emit an info event with a concrete fix path
|
|
377
|
+
# so the user discovers this BEFORE they hit the cryptic n_keep/n_ctx
|
|
378
|
+
# error.
|
|
379
|
+
for m in models:
|
|
380
|
+
if (
|
|
381
|
+
m.state == "loaded"
|
|
382
|
+
and 0 < m.loaded_context_length < LM_STUDIO_MIN_RECOMMENDED_LOADED_CONTEXT
|
|
383
|
+
):
|
|
384
|
+
recommended = min(
|
|
385
|
+
m.max_context_length or LM_STUDIO_MIN_RECOMMENDED_LOADED_CONTEXT,
|
|
386
|
+
131072,
|
|
387
|
+
)
|
|
388
|
+
yield OAuthEvent(
|
|
389
|
+
"info",
|
|
390
|
+
(
|
|
391
|
+
f"⚠ {m.model_id} is loaded with only {m.loaded_context_length} "
|
|
392
|
+
f"tokens of context, but the agent's system prompt needs "
|
|
393
|
+
f"≥{LM_STUDIO_MIN_RECOMMENDED_LOADED_CONTEXT}. Chats will fail "
|
|
394
|
+
"on the first message. "
|
|
395
|
+
"In LM Studio: open the model → gear icon → Context Length → "
|
|
396
|
+
f"set to {recommended} (model max is "
|
|
397
|
+
f"{m.max_context_length or 'unknown'}) → reload the model."
|
|
398
|
+
),
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
async def logout_lm_studio(config: Config) -> AsyncIterator[OAuthEvent]:
|
|
403
|
+
if not config.is_from_default_location:
|
|
404
|
+
yield OAuthEvent(
|
|
405
|
+
"error",
|
|
406
|
+
"Logout requires the default config file; restart without --config/--config-file.",
|
|
407
|
+
)
|
|
408
|
+
return
|
|
409
|
+
|
|
410
|
+
config.providers.pop(LM_STUDIO_PROVIDER_KEY, None)
|
|
411
|
+
for key, model in list(config.models.items()):
|
|
412
|
+
if model.provider == LM_STUDIO_PROVIDER_KEY:
|
|
413
|
+
del config.models[key]
|
|
414
|
+
|
|
415
|
+
if config.default_model not in config.models:
|
|
416
|
+
config.default_model = next(iter(config.models), "")
|
|
417
|
+
save_config(config)
|
|
418
|
+
yield OAuthEvent("success", "Logged out of LM Studio successfully.")
|