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,300 @@
|
|
|
1
|
+
from collections import deque
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import override
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field, model_validator
|
|
6
|
+
from pythinker_core.tooling import CallableTool2, ToolError, ToolOk, ToolReturnValue
|
|
7
|
+
from pythinker_host.path import HostPath
|
|
8
|
+
|
|
9
|
+
from pythinker_code.soul.agent import Runtime
|
|
10
|
+
from pythinker_code.tools.file.utils import MEDIA_SNIFF_BYTES, detect_file_type
|
|
11
|
+
from pythinker_code.tools.utils import load_desc, truncate_line
|
|
12
|
+
from pythinker_code.utils.logging import logger
|
|
13
|
+
from pythinker_code.utils.path import is_within_workspace
|
|
14
|
+
from pythinker_code.utils.sensitive import is_sensitive_file
|
|
15
|
+
|
|
16
|
+
MAX_LINES = 1000
|
|
17
|
+
MAX_LINE_LENGTH = 2000
|
|
18
|
+
MAX_BYTES = 100 << 10 # 100KB
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Params(BaseModel):
|
|
22
|
+
path: str = Field(
|
|
23
|
+
description=(
|
|
24
|
+
"The path to the file to read. Absolute paths are required when reading files "
|
|
25
|
+
"outside the working directory."
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
line_offset: int = Field(
|
|
29
|
+
description=(
|
|
30
|
+
"The line number to start reading from. "
|
|
31
|
+
"By default read from the beginning of the file. "
|
|
32
|
+
"Set this when the file is too large to read at once. "
|
|
33
|
+
"Negative values read from the end of the file (e.g. -100 reads the last 100 lines). "
|
|
34
|
+
f"The absolute value of negative offset cannot exceed {MAX_LINES}."
|
|
35
|
+
),
|
|
36
|
+
default=1,
|
|
37
|
+
)
|
|
38
|
+
n_lines: int = Field(
|
|
39
|
+
description=(
|
|
40
|
+
"The number of lines to read. "
|
|
41
|
+
f"By default read up to {MAX_LINES} lines, which is the max allowed value. "
|
|
42
|
+
"Set this value when the file is too large to read at once."
|
|
43
|
+
),
|
|
44
|
+
default=MAX_LINES,
|
|
45
|
+
ge=1,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
@model_validator(mode="after")
|
|
49
|
+
def _validate_line_offset(self) -> "Params":
|
|
50
|
+
if self.line_offset == 0:
|
|
51
|
+
raise ValueError(
|
|
52
|
+
"line_offset cannot be 0; use 1 for the first line or -1 for the last line"
|
|
53
|
+
)
|
|
54
|
+
if self.line_offset < -MAX_LINES:
|
|
55
|
+
raise ValueError(
|
|
56
|
+
f"line_offset cannot be less than -{MAX_LINES}. "
|
|
57
|
+
"Use a positive line_offset with the total line count "
|
|
58
|
+
"to read from a specific position."
|
|
59
|
+
)
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ReadFile(CallableTool2[Params]):
|
|
64
|
+
name: str = "ReadFile"
|
|
65
|
+
params: type[Params] = Params
|
|
66
|
+
|
|
67
|
+
def __init__(self, runtime: Runtime) -> None:
|
|
68
|
+
description = load_desc(
|
|
69
|
+
Path(__file__).parent / "read.md",
|
|
70
|
+
{
|
|
71
|
+
"MAX_LINES": MAX_LINES,
|
|
72
|
+
"MAX_LINE_LENGTH": MAX_LINE_LENGTH,
|
|
73
|
+
"MAX_BYTES": MAX_BYTES,
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
super().__init__(description=description)
|
|
77
|
+
self._runtime = runtime
|
|
78
|
+
self._work_dir = runtime.builtin_args.PYTHINKER_WORK_DIR
|
|
79
|
+
self._additional_dirs = runtime.additional_dirs
|
|
80
|
+
|
|
81
|
+
async def _validate_path(self, path: HostPath) -> ToolError | None:
|
|
82
|
+
"""Validate that the path is safe to read."""
|
|
83
|
+
resolved_path = path.canonical()
|
|
84
|
+
|
|
85
|
+
if (
|
|
86
|
+
not is_within_workspace(resolved_path, self._work_dir, self._additional_dirs)
|
|
87
|
+
and not path.is_absolute()
|
|
88
|
+
):
|
|
89
|
+
# Outside files can only be read with absolute paths
|
|
90
|
+
return ToolError(
|
|
91
|
+
message=(
|
|
92
|
+
f"`{path}` is not an absolute path. "
|
|
93
|
+
"You must provide an absolute path to read a file "
|
|
94
|
+
"outside the working directory."
|
|
95
|
+
),
|
|
96
|
+
brief="Invalid path",
|
|
97
|
+
)
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
@override
|
|
101
|
+
async def __call__(self, params: Params) -> ToolReturnValue:
|
|
102
|
+
if not params.path:
|
|
103
|
+
return ToolError(
|
|
104
|
+
message="File path cannot be empty.",
|
|
105
|
+
brief="Empty file path",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
p = HostPath(params.path).expanduser()
|
|
110
|
+
if err := await self._validate_path(p):
|
|
111
|
+
return err
|
|
112
|
+
p = p.canonical()
|
|
113
|
+
|
|
114
|
+
if is_sensitive_file(str(p)):
|
|
115
|
+
return ToolError(
|
|
116
|
+
message=(
|
|
117
|
+
f"`{params.path}` appears to contain secrets "
|
|
118
|
+
"(matched sensitive file pattern). "
|
|
119
|
+
"Reading this file is blocked to protect credentials."
|
|
120
|
+
),
|
|
121
|
+
brief="Sensitive file",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
if not await p.exists():
|
|
125
|
+
return ToolError(
|
|
126
|
+
message=f"`{params.path}` does not exist.",
|
|
127
|
+
brief="File not found",
|
|
128
|
+
)
|
|
129
|
+
if not await p.is_file():
|
|
130
|
+
return ToolError(
|
|
131
|
+
message=f"`{params.path}` is not a file.",
|
|
132
|
+
brief="Invalid path",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
header = await p.read_bytes(MEDIA_SNIFF_BYTES)
|
|
136
|
+
file_type = detect_file_type(str(p), header=header)
|
|
137
|
+
if file_type.kind in ("image", "video"):
|
|
138
|
+
return ToolError(
|
|
139
|
+
message=(
|
|
140
|
+
f"`{params.path}` is a {file_type.kind} file. "
|
|
141
|
+
"Use other appropriate tools to read image or video files."
|
|
142
|
+
),
|
|
143
|
+
brief="Unsupported file type",
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if file_type.kind == "unknown":
|
|
147
|
+
return ToolError(
|
|
148
|
+
message=(
|
|
149
|
+
f"`{params.path}` seems not readable. "
|
|
150
|
+
"You may need to read it with proper shell commands, Python tools "
|
|
151
|
+
"or MCP tools if available. "
|
|
152
|
+
"If you read/operate it with Python, you MUST ensure that any "
|
|
153
|
+
"third-party packages are installed in a virtual environment (venv)."
|
|
154
|
+
),
|
|
155
|
+
brief="File not readable",
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
assert params.n_lines >= 1
|
|
159
|
+
assert params.line_offset != 0
|
|
160
|
+
|
|
161
|
+
if params.line_offset < 0:
|
|
162
|
+
return await self._read_tail(p, params)
|
|
163
|
+
else:
|
|
164
|
+
return await self._read_forward(p, params)
|
|
165
|
+
except Exception as e:
|
|
166
|
+
logger.warning("ReadFile failed: {path}: {error}", path=params.path, error=e)
|
|
167
|
+
return ToolError(
|
|
168
|
+
message=f"Failed to read {params.path}. Error: {e}",
|
|
169
|
+
brief="Failed to read file",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
async def _read_forward(self, p: HostPath, params: Params) -> ToolReturnValue:
|
|
173
|
+
"""Read file from a positive line_offset, counting total lines."""
|
|
174
|
+
lines: list[str] = []
|
|
175
|
+
n_bytes = 0
|
|
176
|
+
truncated_line_numbers: list[int] = []
|
|
177
|
+
max_lines_reached = False
|
|
178
|
+
max_bytes_reached = False
|
|
179
|
+
collecting = True # False once we've collected enough lines
|
|
180
|
+
current_line_no = 0
|
|
181
|
+
async for line in p.read_lines(errors="replace"):
|
|
182
|
+
current_line_no += 1
|
|
183
|
+
if not collecting:
|
|
184
|
+
continue
|
|
185
|
+
if current_line_no < params.line_offset:
|
|
186
|
+
continue
|
|
187
|
+
truncated = truncate_line(line, MAX_LINE_LENGTH)
|
|
188
|
+
if truncated != line:
|
|
189
|
+
truncated_line_numbers.append(current_line_no)
|
|
190
|
+
lines.append(truncated)
|
|
191
|
+
n_bytes += len(truncated.encode("utf-8"))
|
|
192
|
+
if len(lines) >= params.n_lines:
|
|
193
|
+
collecting = False
|
|
194
|
+
elif len(lines) >= MAX_LINES:
|
|
195
|
+
max_lines_reached = True
|
|
196
|
+
collecting = False
|
|
197
|
+
elif n_bytes >= MAX_BYTES:
|
|
198
|
+
max_bytes_reached = True
|
|
199
|
+
collecting = False
|
|
200
|
+
|
|
201
|
+
total_lines = current_line_no
|
|
202
|
+
|
|
203
|
+
# Format output with line numbers like `cat -n`
|
|
204
|
+
start_line = params.line_offset
|
|
205
|
+
lines_with_no: list[str] = []
|
|
206
|
+
for line_num, line in zip(range(start_line, start_line + len(lines)), lines, strict=True):
|
|
207
|
+
lines_with_no.append(f"{line_num:6d}\t{line}")
|
|
208
|
+
|
|
209
|
+
message = (
|
|
210
|
+
f"{len(lines)} lines read from file starting from line {start_line}."
|
|
211
|
+
if len(lines) > 0
|
|
212
|
+
else "No lines read from file."
|
|
213
|
+
)
|
|
214
|
+
message += f" Total lines in file: {total_lines}."
|
|
215
|
+
if max_lines_reached:
|
|
216
|
+
message += f" Max {MAX_LINES} lines reached."
|
|
217
|
+
elif max_bytes_reached:
|
|
218
|
+
message += f" Max {MAX_BYTES} bytes reached."
|
|
219
|
+
elif len(lines) < params.n_lines:
|
|
220
|
+
message += " End of file reached."
|
|
221
|
+
if truncated_line_numbers:
|
|
222
|
+
message += f" Lines {truncated_line_numbers} were truncated."
|
|
223
|
+
return ToolOk(
|
|
224
|
+
output="".join(lines_with_no),
|
|
225
|
+
message=message,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
async def _read_tail(self, p: HostPath, params: Params) -> ToolReturnValue:
|
|
229
|
+
"""Read file from a negative line_offset (tail mode)."""
|
|
230
|
+
tail_count = abs(params.line_offset)
|
|
231
|
+
|
|
232
|
+
# Use a deque to keep the last `tail_count` lines with their line numbers
|
|
233
|
+
# Each entry: (line_no, truncated_line, was_truncated)
|
|
234
|
+
tail_buf: deque[tuple[int, str, bool]] = deque(maxlen=tail_count)
|
|
235
|
+
current_line_no = 0
|
|
236
|
+
async for line in p.read_lines(errors="replace"):
|
|
237
|
+
current_line_no += 1
|
|
238
|
+
truncated = truncate_line(line, MAX_LINE_LENGTH)
|
|
239
|
+
tail_buf.append((current_line_no, truncated, truncated != line))
|
|
240
|
+
|
|
241
|
+
total_lines = current_line_no
|
|
242
|
+
|
|
243
|
+
# Step 1: Apply n_lines / MAX_LINES from head of tail_buf.
|
|
244
|
+
# This preserves the user's requested start position.
|
|
245
|
+
all_entries = list(tail_buf)
|
|
246
|
+
line_limit = min(params.n_lines, MAX_LINES)
|
|
247
|
+
candidates = all_entries[:line_limit]
|
|
248
|
+
max_lines_reached = len(all_entries) > MAX_LINES and len(candidates) == MAX_LINES
|
|
249
|
+
|
|
250
|
+
# Step 2: Apply MAX_BYTES — if candidates exceed the byte budget,
|
|
251
|
+
# reverse-scan to keep the newest (closest to EOF) lines that fit.
|
|
252
|
+
total_candidate_bytes = sum(len(entry[1].encode("utf-8")) for entry in candidates)
|
|
253
|
+
if total_candidate_bytes > MAX_BYTES:
|
|
254
|
+
max_bytes_reached = True
|
|
255
|
+
kept = 0
|
|
256
|
+
n_bytes = 0
|
|
257
|
+
for entry in reversed(candidates):
|
|
258
|
+
n_bytes += len(entry[1].encode("utf-8"))
|
|
259
|
+
if n_bytes > MAX_BYTES:
|
|
260
|
+
break
|
|
261
|
+
kept += 1
|
|
262
|
+
candidates = candidates[len(candidates) - kept :]
|
|
263
|
+
else:
|
|
264
|
+
max_bytes_reached = False
|
|
265
|
+
|
|
266
|
+
# Step 3: Collect results from candidates
|
|
267
|
+
lines: list[str] = []
|
|
268
|
+
line_numbers: list[int] = []
|
|
269
|
+
truncated_line_numbers: list[int] = []
|
|
270
|
+
|
|
271
|
+
for line_no, truncated, was_truncated in candidates:
|
|
272
|
+
if was_truncated:
|
|
273
|
+
truncated_line_numbers.append(line_no)
|
|
274
|
+
lines.append(truncated)
|
|
275
|
+
line_numbers.append(line_no)
|
|
276
|
+
|
|
277
|
+
# Format output with absolute line numbers
|
|
278
|
+
lines_with_no: list[str] = []
|
|
279
|
+
for line_num, line in zip(line_numbers, lines, strict=True):
|
|
280
|
+
lines_with_no.append(f"{line_num:6d}\t{line}")
|
|
281
|
+
|
|
282
|
+
start_line = line_numbers[0] if line_numbers else total_lines + 1
|
|
283
|
+
message = (
|
|
284
|
+
f"{len(lines)} lines read from file starting from line {start_line}."
|
|
285
|
+
if len(lines) > 0
|
|
286
|
+
else "No lines read from file."
|
|
287
|
+
)
|
|
288
|
+
message += f" Total lines in file: {total_lines}."
|
|
289
|
+
if max_lines_reached:
|
|
290
|
+
message += f" Max {MAX_LINES} lines reached."
|
|
291
|
+
elif max_bytes_reached:
|
|
292
|
+
message += f" Max {MAX_BYTES} bytes reached."
|
|
293
|
+
elif len(lines) < params.n_lines:
|
|
294
|
+
message += " End of file reached."
|
|
295
|
+
if truncated_line_numbers:
|
|
296
|
+
message += f" Lines {truncated_line_numbers} were truncated."
|
|
297
|
+
return ToolOk(
|
|
298
|
+
output="".join(lines_with_no),
|
|
299
|
+
message=message,
|
|
300
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Read media content from a file.
|
|
2
|
+
|
|
3
|
+
**Tips:**
|
|
4
|
+
- Make sure you follow the description of each tool parameter.
|
|
5
|
+
- A `<system>` tag will be given before the read file content.
|
|
6
|
+
- The system will notify you when there is anything wrong when reading the file.
|
|
7
|
+
- This tool is a tool that you typically want to use in parallel. Always read multiple files in one response when possible.
|
|
8
|
+
- This tool can only read image or video files. To read other types of files, use the ReadFile tool. To list directories, use the Glob tool or `ls` command via the Shell tool.
|
|
9
|
+
- If the file doesn't exist or path is invalid, an error will be returned.
|
|
10
|
+
- The maximum size that can be read is ${MAX_MEDIA_MEGABYTES}MB. An error will be returned if the file is larger than this limit.
|
|
11
|
+
- The media content will be returned in a form that you can directly view and understand.
|
|
12
|
+
|
|
13
|
+
**Capabilities**
|
|
14
|
+
{% if "image_in" in capabilities and "video_in" in capabilities %}
|
|
15
|
+
- This tool supports image and video files for the current model.
|
|
16
|
+
{% elif "image_in" in capabilities %}
|
|
17
|
+
- This tool supports image files for the current model.
|
|
18
|
+
- Video files are not supported by the current model.
|
|
19
|
+
{% elif "video_in" in capabilities %}
|
|
20
|
+
- This tool supports video files for the current model.
|
|
21
|
+
- Image files are not supported by the current model.
|
|
22
|
+
{% else %}
|
|
23
|
+
- The current model does not support image or video input.
|
|
24
|
+
{% endif %}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from io import BytesIO
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import override
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
from pythinker_core.chat_provider.pythinker import Pythinker
|
|
8
|
+
from pythinker_core.tooling import CallableTool2, ToolError, ToolOk, ToolReturnValue
|
|
9
|
+
from pythinker_host.path import HostPath
|
|
10
|
+
|
|
11
|
+
from pythinker_code.soul.agent import Runtime
|
|
12
|
+
from pythinker_code.tools import SkipThisTool
|
|
13
|
+
from pythinker_code.tools.file.utils import MEDIA_SNIFF_BYTES, FileType, detect_file_type
|
|
14
|
+
from pythinker_code.tools.utils import load_desc
|
|
15
|
+
from pythinker_code.utils.logging import logger
|
|
16
|
+
from pythinker_code.utils.media_tags import wrap_media_part
|
|
17
|
+
from pythinker_code.utils.path import is_within_workspace
|
|
18
|
+
from pythinker_code.wire.types import ImageURLPart, VideoURLPart
|
|
19
|
+
|
|
20
|
+
MAX_MEDIA_MEGABYTES = 100
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _to_data_url(mime_type: str, data: bytes) -> str:
|
|
24
|
+
encoded = base64.b64encode(data).decode("utf-8", errors="replace")
|
|
25
|
+
return f"data:{mime_type};base64,{encoded}"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _extract_image_size(data: bytes) -> tuple[int, int] | None:
|
|
29
|
+
try:
|
|
30
|
+
from PIL import Image
|
|
31
|
+
except Exception:
|
|
32
|
+
return None
|
|
33
|
+
try:
|
|
34
|
+
with Image.open(BytesIO(data)) as image:
|
|
35
|
+
image.load()
|
|
36
|
+
return image.size
|
|
37
|
+
except Exception:
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Params(BaseModel):
|
|
42
|
+
path: str = Field(
|
|
43
|
+
description=(
|
|
44
|
+
"The path to the file to read. Absolute paths are required when reading files "
|
|
45
|
+
"outside the working directory."
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ReadMediaFile(CallableTool2[Params]):
|
|
51
|
+
name: str = "ReadMediaFile"
|
|
52
|
+
params: type[Params] = Params
|
|
53
|
+
|
|
54
|
+
def __init__(self, runtime: Runtime) -> None:
|
|
55
|
+
capabilities = runtime.llm.capabilities if runtime.llm else set[str]()
|
|
56
|
+
if "image_in" not in capabilities and "video_in" not in capabilities:
|
|
57
|
+
raise SkipThisTool()
|
|
58
|
+
|
|
59
|
+
description = load_desc(
|
|
60
|
+
Path(__file__).parent / "read_media.md",
|
|
61
|
+
{
|
|
62
|
+
"MAX_MEDIA_MEGABYTES": MAX_MEDIA_MEGABYTES,
|
|
63
|
+
"capabilities": capabilities,
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
super().__init__(description=description)
|
|
67
|
+
|
|
68
|
+
self._runtime = runtime
|
|
69
|
+
self._work_dir = runtime.builtin_args.PYTHINKER_WORK_DIR
|
|
70
|
+
self._additional_dirs = runtime.additional_dirs
|
|
71
|
+
self._capabilities = capabilities
|
|
72
|
+
|
|
73
|
+
async def _validate_path(self, path: HostPath) -> ToolError | None:
|
|
74
|
+
"""Validate that the path is safe to read."""
|
|
75
|
+
resolved_path = path.canonical()
|
|
76
|
+
|
|
77
|
+
if (
|
|
78
|
+
not is_within_workspace(resolved_path, self._work_dir, self._additional_dirs)
|
|
79
|
+
and not path.is_absolute()
|
|
80
|
+
):
|
|
81
|
+
# Outside files can only be read with absolute paths
|
|
82
|
+
return ToolError(
|
|
83
|
+
message=(
|
|
84
|
+
f"`{path}` is not an absolute path. "
|
|
85
|
+
"You must provide an absolute path to read a file "
|
|
86
|
+
"outside the working directory."
|
|
87
|
+
),
|
|
88
|
+
brief="Invalid path",
|
|
89
|
+
)
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
async def _read_media(self, path: HostPath, file_type: FileType) -> ToolReturnValue:
|
|
93
|
+
assert file_type.kind in ("image", "video")
|
|
94
|
+
|
|
95
|
+
media_path = str(path)
|
|
96
|
+
stat = await path.stat()
|
|
97
|
+
size = stat.st_size
|
|
98
|
+
if size == 0:
|
|
99
|
+
return ToolError(
|
|
100
|
+
message=f"`{path}` is empty.",
|
|
101
|
+
brief="Empty file",
|
|
102
|
+
)
|
|
103
|
+
if size > (MAX_MEDIA_MEGABYTES << 20):
|
|
104
|
+
return ToolError(
|
|
105
|
+
message=(
|
|
106
|
+
f"`{path}` is {size} bytes, which exceeds the max "
|
|
107
|
+
f"{MAX_MEDIA_MEGABYTES}MB bytes for media files."
|
|
108
|
+
),
|
|
109
|
+
brief="File too large",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
match file_type.kind:
|
|
113
|
+
case "image":
|
|
114
|
+
data = await path.read_bytes()
|
|
115
|
+
data_url = _to_data_url(file_type.mime_type, data)
|
|
116
|
+
part = ImageURLPart(image_url=ImageURLPart.ImageURL(url=data_url))
|
|
117
|
+
wrapped = wrap_media_part(part, tag="image", attrs={"path": media_path})
|
|
118
|
+
image_size = _extract_image_size(data)
|
|
119
|
+
case "video":
|
|
120
|
+
data = await path.read_bytes()
|
|
121
|
+
if (llm := self._runtime.llm) and isinstance(llm.chat_provider, Pythinker):
|
|
122
|
+
part = await llm.chat_provider.files.upload_video(
|
|
123
|
+
data=data,
|
|
124
|
+
mime_type=file_type.mime_type,
|
|
125
|
+
)
|
|
126
|
+
wrapped = wrap_media_part(part, tag="video", attrs={"path": media_path})
|
|
127
|
+
else:
|
|
128
|
+
data_url = _to_data_url(file_type.mime_type, data)
|
|
129
|
+
part = VideoURLPart(video_url=VideoURLPart.VideoURL(url=data_url))
|
|
130
|
+
wrapped = wrap_media_part(part, tag="video", attrs={"path": media_path})
|
|
131
|
+
image_size = None
|
|
132
|
+
|
|
133
|
+
size_hint = ""
|
|
134
|
+
if image_size:
|
|
135
|
+
size_hint = f", original size {image_size[0]}x{image_size[1]}px"
|
|
136
|
+
note = (
|
|
137
|
+
" If you need to output coordinates, output relative coordinates first and "
|
|
138
|
+
"compute absolute coordinates using the original image size; if you generate or "
|
|
139
|
+
"edit images/videos via commands or scripts, read the result back immediately "
|
|
140
|
+
"before continuing."
|
|
141
|
+
)
|
|
142
|
+
return ToolOk(
|
|
143
|
+
output=wrapped,
|
|
144
|
+
message=(
|
|
145
|
+
f"Loaded {file_type.kind} file `{path}` "
|
|
146
|
+
f"({file_type.mime_type}, {size} bytes{size_hint}).{note}"
|
|
147
|
+
),
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
@override
|
|
151
|
+
async def __call__(self, params: Params) -> ToolReturnValue:
|
|
152
|
+
if not params.path:
|
|
153
|
+
return ToolError(
|
|
154
|
+
message="File path cannot be empty.",
|
|
155
|
+
brief="Empty file path",
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
p = HostPath(params.path).expanduser()
|
|
160
|
+
if err := await self._validate_path(p):
|
|
161
|
+
return err
|
|
162
|
+
p = p.canonical()
|
|
163
|
+
|
|
164
|
+
if not await p.exists():
|
|
165
|
+
return ToolError(
|
|
166
|
+
message=f"`{params.path}` does not exist.",
|
|
167
|
+
brief="File not found",
|
|
168
|
+
)
|
|
169
|
+
if not await p.is_file():
|
|
170
|
+
return ToolError(
|
|
171
|
+
message=f"`{params.path}` is not a file.",
|
|
172
|
+
brief="Invalid path",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
header = await p.read_bytes(MEDIA_SNIFF_BYTES)
|
|
176
|
+
file_type = detect_file_type(str(p), header=header)
|
|
177
|
+
if file_type.kind == "text":
|
|
178
|
+
return ToolError(
|
|
179
|
+
message=f"`{params.path}` is a text file. Use ReadFile to read text files.",
|
|
180
|
+
brief="Unsupported file type",
|
|
181
|
+
)
|
|
182
|
+
if file_type.kind == "unknown":
|
|
183
|
+
return ToolError(
|
|
184
|
+
message=(
|
|
185
|
+
f"`{params.path}` seems not readable as an image or video file. "
|
|
186
|
+
"You may need to read it with proper shell commands, Python tools "
|
|
187
|
+
"or MCP tools if available. "
|
|
188
|
+
"If you read/operate it with Python, you MUST ensure that any "
|
|
189
|
+
"third-party packages are installed in a virtual environment (venv)."
|
|
190
|
+
),
|
|
191
|
+
brief="File not readable",
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if file_type.kind == "image" and "image_in" not in self._capabilities:
|
|
195
|
+
return ToolError(
|
|
196
|
+
message=(
|
|
197
|
+
"The current model does not support image input. "
|
|
198
|
+
"Tell the user to use a model with image input capability."
|
|
199
|
+
),
|
|
200
|
+
brief="Unsupported media type",
|
|
201
|
+
)
|
|
202
|
+
if file_type.kind == "video" and "video_in" not in self._capabilities:
|
|
203
|
+
return ToolError(
|
|
204
|
+
message=(
|
|
205
|
+
"The current model does not support video input. "
|
|
206
|
+
"Tell the user to use a model with video input capability."
|
|
207
|
+
),
|
|
208
|
+
brief="Unsupported media type",
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
return await self._read_media(p, file_type)
|
|
212
|
+
except Exception as e:
|
|
213
|
+
logger.warning("ReadMediaFile failed: {path}: {error}", path=params.path, error=e)
|
|
214
|
+
return ToolError(
|
|
215
|
+
message=f"Failed to read {params.path}. Error: {e}",
|
|
216
|
+
brief="Failed to read file",
|
|
217
|
+
)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Replace specific strings within a specified file.
|
|
2
|
+
|
|
3
|
+
**Tips:**
|
|
4
|
+
- Only use this tool on text files.
|
|
5
|
+
- Multi-line strings are supported.
|
|
6
|
+
- Can specify a single edit or a list of edits in one call.
|
|
7
|
+
- You should prefer this tool over WriteFile tool and Shell `sed` command.
|