limbo-code 0.1.0__tar.gz → 0.1__tar.gz
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.
- {limbo_code-0.1.0 → limbo_code-0.1}/.github/workflows/test.yml +9 -3
- {limbo_code-0.1.0 → limbo_code-0.1}/.gitignore +1 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/AGENTS.md +33 -9
- limbo_code-0.1/PKG-INFO +267 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/README.md +93 -2
- limbo_code-0.1/docs/assets/walkthrough/limbo-dark/1_idle.svg +165 -0
- limbo_code-0.1/docs/assets/walkthrough/limbo-dark/2_thinking_tool_running.svg +166 -0
- limbo_code-0.1/docs/assets/walkthrough/limbo-dark/3_tool_success.svg +167 -0
- limbo_code-0.1/docs/assets/walkthrough/limbo-dark/4_tool_error.svg +168 -0
- limbo_code-0.1/docs/assets/walkthrough/limbo-dark/5_llm_error.svg +166 -0
- limbo_code-0.1/docs/assets/walkthrough/limbo-dark/6_edit_diff.svg +168 -0
- limbo_code-0.1/docs/assets/walkthrough/limbo-light/1_idle.svg +166 -0
- limbo_code-0.1/docs/assets/walkthrough/limbo-light/2_thinking_tool_running.svg +167 -0
- limbo_code-0.1/docs/assets/walkthrough/limbo-light/3_tool_success.svg +168 -0
- limbo_code-0.1/docs/assets/walkthrough/limbo-light/4_tool_error.svg +169 -0
- limbo_code-0.1/docs/assets/walkthrough/limbo-light/5_llm_error.svg +167 -0
- limbo_code-0.1/docs/assets/walkthrough/limbo-light/6_edit_diff.svg +169 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/docs/skills.md +14 -2
- {limbo_code-0.1.0 → limbo_code-0.1}/pyproject.toml +16 -3
- limbo_code-0.1/scripts/check_contrast.py +28 -0
- limbo_code-0.1/scripts/gen_banner.py +103 -0
- limbo_code-0.1/scripts/ui_walkthrough.py +130 -0
- limbo_code-0.1/src/limbo/__init__.py +8 -0
- limbo_code-0.1/src/limbo/agent.py +1113 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/app.py +12 -8
- limbo_code-0.1/src/limbo/compaction.py +189 -0
- limbo_code-0.1/src/limbo/config.py +256 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/history.py +0 -19
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/llm/anthropic_client.py +89 -36
- limbo_code-0.1/src/limbo/llm/catalog.py +396 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/llm/client.py +6 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/llm/factory.py +3 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/llm/openai_client.py +98 -22
- limbo_code-0.1/src/limbo/llm/responses_client.py +409 -0
- limbo_code-0.1/src/limbo/llm/retry.py +203 -0
- limbo_code-0.1/src/limbo/llm/sse.py +31 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/models.py +36 -2
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/sessions.py +96 -6
- limbo_code-0.1/src/limbo/skills.py +190 -0
- limbo_code-0.1/src/limbo/tools/base.py +294 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/tools/bash.py +39 -7
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/tools/edit.py +23 -19
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/tools/find.py +12 -6
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/tools/grep.py +18 -10
- limbo_code-0.1/src/limbo/tools/mutation_queue.py +36 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/tools/read.py +56 -18
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/tools/registry.py +35 -2
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/tools/write.py +7 -5
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/ui/app.py +17 -6
- limbo_code-0.1/src/limbo/ui/app.tcss +316 -0
- limbo_code-0.1/src/limbo/ui/banner.py +117 -0
- limbo_code-0.1/src/limbo/ui/clipboard.py +314 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/ui/commands.py +4 -0
- limbo_code-0.1/src/limbo/ui/contrast.py +139 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/ui/screens/main.py +296 -16
- limbo_code-0.1/src/limbo/ui/screens/model_picker.py +122 -0
- limbo_code-0.1/src/limbo/ui/syntax.py +84 -0
- limbo_code-0.1/src/limbo/ui/theme.py +101 -0
- limbo_code-0.1/src/limbo/ui/widgets/chat.py +320 -0
- limbo_code-0.1/src/limbo/ui/widgets/input.py +409 -0
- limbo_code-0.1/src/limbo/ui/widgets/status_bar.py +116 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/ui/widgets/tool_card.py +18 -8
- limbo_code-0.1/src/limbo/user_paths.py +82 -0
- limbo_code-0.1/tests/test_agent.py +1965 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/test_anthropic_client.py +277 -2
- limbo_code-0.1/tests/test_catalog.py +293 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/test_cli.py +12 -0
- limbo_code-0.1/tests/test_clipboard.py +205 -0
- limbo_code-0.1/tests/test_compaction.py +181 -0
- limbo_code-0.1/tests/test_config.py +220 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/test_history.py +0 -13
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/test_llm_client.py +276 -2
- limbo_code-0.1/tests/test_responses_client.py +582 -0
- limbo_code-0.1/tests/test_retry.py +306 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/test_sessions.py +181 -4
- limbo_code-0.1/tests/test_skills.py +187 -0
- limbo_code-0.1/tests/test_user_paths.py +87 -0
- limbo_code-0.1/tests/tools/test_base.py +244 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/tools/test_bash.py +66 -6
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/tools/test_find.py +20 -0
- limbo_code-0.1/tests/tools/test_mutation_queue.py +47 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/tools/test_read.py +89 -2
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/tools/test_registry.py +32 -0
- limbo_code-0.1/tests/ui/__snapshots__/test_snapshots/test_snapshot_idle.raw +172 -0
- limbo_code-0.1/tests/ui/__snapshots__/test_snapshots/test_snapshot_session_picker.raw +168 -0
- limbo_code-0.1/tests/ui/__snapshots__/test_snapshots/test_snapshot_thinking_and_running_tool.raw +174 -0
- limbo_code-0.1/tests/ui/__snapshots__/test_snapshots/test_snapshot_tool_error_and_error_message.raw +175 -0
- limbo_code-0.1/tests/ui/test_compact_ui.py +188 -0
- limbo_code-0.1/tests/ui/test_contrast.py +41 -0
- limbo_code-0.1/tests/ui/test_input_attachments.py +164 -0
- limbo_code-0.1/tests/ui/test_input_paste.py +314 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/ui/test_main_screen.py +74 -2
- limbo_code-0.1/tests/ui/test_model_picker.py +393 -0
- limbo_code-0.1/tests/ui/test_scroll_follow.py +85 -0
- limbo_code-0.1/tests/ui/test_snapshots.py +136 -0
- limbo_code-0.1/tests/ui/test_startup_art.py +137 -0
- limbo_code-0.1/tests/ui/test_steer_ui.py +532 -0
- limbo_code-0.1/tests/ui/test_theme.py +104 -0
- limbo_code-0.1/uv.lock +936 -0
- limbo_code-0.1.0/PKG-INFO +0 -16
- limbo_code-0.1.0/src/limbo/__init__.py +0 -3
- limbo_code-0.1.0/src/limbo/agent.py +0 -456
- limbo_code-0.1.0/src/limbo/config.py +0 -99
- limbo_code-0.1.0/src/limbo/llm/catalog.py +0 -234
- limbo_code-0.1.0/src/limbo/skills.py +0 -89
- limbo_code-0.1.0/src/limbo/tools/base.py +0 -102
- limbo_code-0.1.0/src/limbo/ui/app.tcss +0 -174
- limbo_code-0.1.0/src/limbo/ui/banner.py +0 -83
- limbo_code-0.1.0/src/limbo/ui/widgets/chat.py +0 -149
- limbo_code-0.1.0/src/limbo/ui/widgets/input.py +0 -199
- limbo_code-0.1.0/src/limbo/ui/widgets/status_bar.py +0 -32
- limbo_code-0.1.0/tests/test_agent.py +0 -729
- limbo_code-0.1.0/tests/test_catalog.py +0 -160
- limbo_code-0.1.0/tests/test_config.py +0 -83
- limbo_code-0.1.0/tests/test_skills.py +0 -89
- limbo_code-0.1.0/tests/tools/test_base.py +0 -85
- limbo_code-0.1.0/tests/ui/test_startup_art.py +0 -94
- {limbo_code-0.1.0 → limbo_code-0.1}/.agents/skills/grill-with-docs/SKILL.md +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/.agents/skills/grill-with-docs/agents/openai.yaml +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/.agents/skills/improve-codebase-architecture/HTML-REPORT.md +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/.agents/skills/improve-codebase-architecture/SKILL.md +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/.agents/skills/improve-codebase-architecture/agents/openai.yaml +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/.agents/skills/tdd/SKILL.md +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/.agents/skills/tdd/agents/openai.yaml +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/.agents/skills/tdd/mocking.md +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/.agents/skills/tdd/tests.md +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/.github/workflows/publish.yml +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/design/confirm_view.html +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/design/limbo-ui-minimal.md +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/design/limbo-ui-redesign.md +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/design/prototype-minimal-confirm.png +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/design/prototype-minimal.html +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/design/prototype-minimal.png +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/design/prototype.html +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/design/prototype.png +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/docs/assets/limbo-current-ui.png +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/docs/assets/limbo-new-ui.png +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/docs/session-management.md +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/docs/ui-redesign-proposal.md +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/skills-lock.json +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/__main__.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/llm/__init__.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/tools/__init__.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/tools/ignore.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/tools/ls.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/trace.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/ui/__init__.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/ui/screens/__init__.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/ui/screens/game2048.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/ui/screens/session_picker.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/ui/widgets/__init__.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/src/limbo/ui/widgets/command_menu.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/test_integration.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/test_models.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/test_trace.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/tools/test_edit.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/tools/test_grep.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/tools/test_ignore.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/tools/test_ls.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/tools/test_write.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/ui/test_app_smoke.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/ui/test_command_menu.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/ui/test_commands.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/ui/test_game2048.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/ui/test_input_history.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/ui/test_sessions_ui.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/ui/test_skills_ui.py +0 -0
- {limbo_code-0.1.0 → limbo_code-0.1}/tests/ui/test_widgets.py +0 -0
|
@@ -17,14 +17,20 @@ jobs:
|
|
|
17
17
|
steps:
|
|
18
18
|
- uses: actions/checkout@v4
|
|
19
19
|
|
|
20
|
+
- name: Install uv
|
|
21
|
+
uses: astral-sh/setup-uv@v5
|
|
22
|
+
|
|
20
23
|
- name: Set up Python
|
|
21
24
|
uses: actions/setup-python@v5
|
|
22
25
|
with:
|
|
23
26
|
python-version: ${{ matrix.python-version }}
|
|
24
|
-
cache: pip
|
|
25
27
|
|
|
28
|
+
# Install from uv.lock so CI renders snapshots with the exact same
|
|
29
|
+
# textual/rich/syrupy versions the baselines were generated with.
|
|
26
30
|
- name: Install dependencies
|
|
27
|
-
run:
|
|
31
|
+
run: uv sync --frozen --extra dev
|
|
32
|
+
env:
|
|
33
|
+
UV_PYTHON: ${{ matrix.python-version }}
|
|
28
34
|
|
|
29
35
|
- name: Run tests
|
|
30
|
-
run: python -m pytest -v
|
|
36
|
+
run: uv run python -m pytest -v
|
|
@@ -44,17 +44,25 @@ src/limbo/
|
|
|
44
44
|
└── ui/
|
|
45
45
|
├── app.py # Textual App subclass (CSS_PATH = app.tcss)
|
|
46
46
|
├── app.tcss # Centralized stylesheet for the whole TUI
|
|
47
|
+
├── theme.py # limbo-dark / limbo-light Theme definitions (RFC LIM-16 palette)
|
|
48
|
+
├── syntax.py # Pygments styles matching the theme palette (tool-card highlighting)
|
|
49
|
+
├── contrast.py # WCAG contrast checker over the theme palettes
|
|
47
50
|
├── commands.py # SlashCommandRegistry: menu metadata + dispatch in one place
|
|
48
51
|
├── screens/
|
|
49
52
|
│ └── main.py # Single-column chat screen + event handling + slash commands
|
|
50
53
|
│ └── session_picker.py # Modal session switcher (/sessions)
|
|
54
|
+
│ └── model_picker.py # Modal model switcher (/model), grouped by provider
|
|
51
55
|
└── widgets/
|
|
52
|
-
├── chat.py # Chat flow: user/assistant(Markdown)/tool cards/errors
|
|
56
|
+
├── chat.py # Chat flow: user/assistant(Markdown)/tool cards/errors + scroll-follow
|
|
53
57
|
├── input.py # Multi-line user input
|
|
54
|
-
├── status_bar.py # Top status bar:
|
|
58
|
+
├── status_bar.py # Top status bar: spinner state + elapsed + tokens + model/workdir
|
|
55
59
|
├── tool_card.py # Inline tool-call card (one-line summary, expandable)
|
|
56
60
|
└── confirm.py # Confirmation modal (ConfirmDialog, Confirmed/Rejected events)
|
|
57
61
|
|
|
62
|
+
scripts/
|
|
63
|
+
├── check_contrast.py # Palette contrast report/CI gate (P2-3)
|
|
64
|
+
└── ui_walkthrough.py # Render 5 key UI states to SVG for visual review (§7.4)
|
|
65
|
+
|
|
58
66
|
tests/
|
|
59
67
|
├── test_models.py
|
|
60
68
|
├── test_config.py
|
|
@@ -85,8 +93,8 @@ tests/
|
|
|
85
93
|
|
|
86
94
|
## AgentLoop Details
|
|
87
95
|
|
|
88
|
-
- `Agent.run()` yields `AgentEvent` types: `TextDelta`, `ToolCallRequest`, `ToolResultEvent`, `ErrorEvent`
|
|
89
|
-
- The loop respects `config.llm.max_iterations` (default
|
|
96
|
+
- `Agent.run()` yields `AgentEvent` types: `TextDelta`, `ThinkingDelta`, `ToolCallRequest`, `ToolResultEvent`, `ErrorEvent`, `UsageUpdate` (cumulative session token count, emitted after each LLM call)
|
|
97
|
+
- The loop respects `config.llm.max_iterations` (default 50)
|
|
90
98
|
- Multi-tool calls in a single assistant turn are executed sequentially
|
|
91
99
|
- When a tool requires confirmation, placeholder `role="tool"` messages are inserted for all remaining calls in that turn so the message history stays valid for the OpenAI API
|
|
92
100
|
- After a confirmed tool is applied, `Agent.continue_after_confirmation()` resumes the loop, first executing remaining placeholders, then returning to the LLM
|
|
@@ -123,36 +131,48 @@ api_key = "..." # Required
|
|
|
123
131
|
model = "deepseek-chat" # Default
|
|
124
132
|
base_url = "https://api.deepseek.com/v1"
|
|
125
133
|
temperature = 0.2
|
|
126
|
-
max_iterations =
|
|
134
|
+
max_iterations = 50
|
|
127
135
|
|
|
128
136
|
[tools]
|
|
129
137
|
bash_enabled = true
|
|
130
138
|
|
|
131
139
|
[ui]
|
|
132
|
-
theme = "
|
|
140
|
+
theme = "limbo-dark" # optional: limbo-dark (default) / limbo-light / any Textual built-in
|
|
141
|
+
show_banner = true # startup ASCII art on fresh sessions
|
|
133
142
|
|
|
134
143
|
[safety]
|
|
135
144
|
dangerous_commands = ["rm", "git reset --hard"]
|
|
136
145
|
sensitive_files = [".env", "id_rsa", "id_ed25519", ".ssh"]
|
|
146
|
+
|
|
147
|
+
[providers.glm] # optional per-provider override (base_url / api_key /
|
|
148
|
+
base_url = "..." # api_key_env / headers); beats the global [llm] values
|
|
137
149
|
```
|
|
138
150
|
|
|
139
151
|
## UI Layout
|
|
140
152
|
|
|
141
153
|
Pi-style single-column layout (top to bottom):
|
|
142
|
-
- **Status bar (1 line)**: Agent state on the left (`● idle
|
|
154
|
+
- **Status bar (1 line)**: Agent state on the left (`● idle`, or animated `⠋ thinking… / running <tool>…` with elapsed seconds), model + cumulative tokens + workdir on the right
|
|
143
155
|
- **Chat flow**: The conversation as a single scrolling stream — user messages (`❯` prefix), assistant replies rendered as streaming Markdown, inline tool-call cards (`✓/⏸/✗` one-line summaries, click or `ctrl+o` to expand full output), error lines
|
|
144
156
|
- **Input box**: The only persistent rounded border on screen; Enter submits, Shift+Enter inserts a newline
|
|
145
157
|
- **Hint line (1 line)**: Key hints in muted color
|
|
146
158
|
|
|
147
159
|
Confirmation modal: `ConfirmDialog` shows tool output with `y`/`n`/`Esc` shortcuts and "Apply"/"Reject" buttons.
|
|
148
160
|
|
|
149
|
-
All styles live in `ui/app.tcss`; widgets do not define `DEFAULT_CSS`.
|
|
161
|
+
All styles live in `ui/app.tcss`; widgets do not define `DEFAULT_CSS`. Colors must
|
|
162
|
+
reference theme semantic variables only (no bare hex; 2048 tiles and the mascot
|
|
163
|
+
banner art are exempt). The palette and per-state color rules are defined in
|
|
164
|
+
`ui/theme.py` (RFC LIM-16): `limbo-dark` is the default, `limbo-light` is also
|
|
165
|
+
built in, and `[ui] theme` can select any Textual built-in theme — custom CSS
|
|
166
|
+
variables fall back to the limbo-dark values via `App.get_theme_variable_defaults()`.
|
|
167
|
+
Run `python scripts/check_contrast.py` after touching the palette: all *used*
|
|
168
|
+
text/background pairs must stay ≥ 4.5:1.
|
|
150
169
|
|
|
151
170
|
## Key Design Decisions
|
|
152
171
|
|
|
153
172
|
- **No provider field**: Provider is inferred from `base_url`, `model`, `api_key`
|
|
154
173
|
- **Async Agent, sync tools**: Tools run via `asyncio.to_thread()` — they are synchronous by default but executed in a thread pool
|
|
155
174
|
- **Session persistence**: Full conversation history rewritten on each save (safe for MVP-scale conversations). Sessions are resumable — see [docs/session-management.md](./docs/session-management.md): `limbo --continue` / `--resume <id>`, and in-TUI `/sessions`, `/new`, `/export` commands
|
|
175
|
+
- **Runtime model switching**: `/model` (picker or direct arg) rebuilds the LLM client mid-session — `MainScreen` sets `config.llm.model` first, then `agent.update_llm()` re-resolves the spec (context window, vision gate, compaction budget); the choice is written back to `config.toml` via tomlkit (comment-preserving, `save_model_to_config`). Busy turns refuse the switch
|
|
156
176
|
- **System message**: Built into `Agent._init_system_message()` — tool descriptions and guidelines are hardcoded
|
|
157
177
|
- **Bash safety filter**: Heuristic only — tokenizes the command, checks against pattern list. Known bypass vectors (subshells, command substitution, variable indirection) are documented.
|
|
158
178
|
|
|
@@ -162,9 +182,13 @@ All styles live in `ui/app.tcss`; widgets do not define `DEFAULT_CSS`. The theme
|
|
|
162
182
|
pytest tests/ -v # all tests
|
|
163
183
|
ruff check src tests # lint
|
|
164
184
|
mypy src # type check (strict=false)
|
|
185
|
+
python scripts/check_contrast.py # palette WCAG contrast gate
|
|
186
|
+
python scripts/ui_walkthrough.py # export key UI states as SVGs to docs/assets/walkthrough/
|
|
165
187
|
```
|
|
166
188
|
|
|
167
|
-
Tests use `pytest-asyncio` for async tests, `respx` for HTTP mocking (LLM client),
|
|
189
|
+
Tests use `pytest-asyncio` for async tests, `respx` for HTTP mocking (LLM client),
|
|
190
|
+
temp directories for tool tests, and `pytest-textual-snapshot` for UI snapshots
|
|
191
|
+
(`pytest --snapshot-update` after intentional visual changes; review the SVG diff).
|
|
168
192
|
|
|
169
193
|
## Common Patterns
|
|
170
194
|
|
limbo_code-0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: limbo-code
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: A minimal terminal AI coding agent
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: httpx>=0.27
|
|
7
|
+
Requires-Dist: openai>=1.30
|
|
8
|
+
Requires-Dist: pydantic>=2.0
|
|
9
|
+
Requires-Dist: pygments>=2.17
|
|
10
|
+
Requires-Dist: textual>=8.0
|
|
11
|
+
Requires-Dist: toml>=0.10
|
|
12
|
+
Requires-Dist: tomlkit>=0.13
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
16
|
+
Requires-Dist: pytest-textual-snapshot>=1.0; extra == 'dev'
|
|
17
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
18
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
19
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
20
|
+
Requires-Dist: types-pygments>=2.17; extra == 'dev'
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# Limbo
|
|
24
|
+
|
|
25
|
+
A minimal terminal AI coding agent.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
Requires Python 3.11 or later.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install -e .
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Configure
|
|
36
|
+
|
|
37
|
+
Create `~/.limbo/config.toml`:
|
|
38
|
+
|
|
39
|
+
```toml
|
|
40
|
+
[llm]
|
|
41
|
+
api_key = "your-api-key"
|
|
42
|
+
model = "deepseek-chat"
|
|
43
|
+
base_url = "https://api.deepseek.com/v1"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Limbo speaks to LLMs through a **provider/model catalog**
|
|
47
|
+
(`src/limbo/llm/catalog.py`). Each provider declares its API dialect,
|
|
48
|
+
endpoint, and credential env var; each model carries its own context window,
|
|
49
|
+
max output tokens, and thinking/reasoning behavior. A client factory
|
|
50
|
+
(`src/limbo/llm/factory.py`) picks the client implementation from the
|
|
51
|
+
provider's API dialect, so non-OpenAI dialects can be added without touching
|
|
52
|
+
call sites. Models not in the catalog fall back to generic
|
|
53
|
+
OpenAI-compatible defaults driven by `base_url` + `model` + `api_key`.
|
|
54
|
+
|
|
55
|
+
Built-in providers:
|
|
56
|
+
|
|
57
|
+
| Provider | API dialect | Endpoint | Key env var |
|
|
58
|
+
|----------|-------------|----------|-------------|
|
|
59
|
+
| `deepseek` | openai-completions | `https://api.deepseek.com/v1` | `DEEPSEEK_API_KEY` |
|
|
60
|
+
| `moonshotai` (Kimi) | openai-completions | `https://api.moonshot.ai/v1` | `MOONSHOT_API_KEY` |
|
|
61
|
+
| `kimi-coding` (Kimi For Coding) | anthropic-messages | `https://api.kimi.com/coding` | `KIMI_API_KEY` |
|
|
62
|
+
| `glm` (GLM Coding Plan) | openai-completions | `https://open.bigmodel.cn/api/coding/paas/v4` | `ZHIPUAI_API_KEY` |
|
|
63
|
+
| `codex` (OpenAI Codex) | openai-responses | `https://api.openai.com/v1` (override with your relay) | `CODEX_API_KEY` |
|
|
64
|
+
|
|
65
|
+
Built-in Codex models include `gpt-5.5`, `gpt-5.6-sol`/`terra`/`luna`,
|
|
66
|
+
`gpt-5.4`, `gpt-5.4-mini`, and `gpt-5.3-codex-spark`. **`gpt-5.5` is
|
|
67
|
+
verified by live test against the target relay** (its 1M context is
|
|
68
|
+
relay-reported); the other entries mirror pi's built-in catalog and are
|
|
69
|
+
unverified — availability depends on the relay in use, and unknown or
|
|
70
|
+
unsupported models fall back to the generic OpenAI-compatible defaults.
|
|
71
|
+
Codex speaks the
|
|
72
|
+
OpenAI **Responses API** (`POST {base_url}/responses`), served by a
|
|
73
|
+
dedicated client (`src/limbo/llm/responses_client.py`, plain httpx SSE):
|
|
74
|
+
system messages become `instructions`, tool definitions are flattened, and
|
|
75
|
+
reasoning models stream `reasoning` summaries and replay encrypted
|
|
76
|
+
reasoning items across turns. Limbo targets **API-key relays**, not the
|
|
77
|
+
ChatGPT subscription backend (OAuth) — point the provider at your relay
|
|
78
|
+
and make sure its model IDs match the catalog:
|
|
79
|
+
|
|
80
|
+
```toml
|
|
81
|
+
[llm]
|
|
82
|
+
model = "gpt-5.5"
|
|
83
|
+
|
|
84
|
+
[providers.codex]
|
|
85
|
+
base_url = "https://my-relay.example.com/v1"
|
|
86
|
+
api_key_env = "CODEX_API_KEY"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Built-in GLM Coding Plan models: `glm-4.7`, `glm-5.1`, `glm-5.2` (1M
|
|
90
|
+
context), `glm-5-turbo`, `glm-5v-turbo` (vision), and `glm-4.5-air`. The
|
|
91
|
+
Coding Plan is a subscription with its own endpoint and keys — a coding-plan
|
|
92
|
+
key only works on the `/api/coding/paas/v4` endpoints (not the pay-per-token
|
|
93
|
+
`/api/paas/v4` ones) and vice versa. For the international endpoint set
|
|
94
|
+
`[providers.glm] base_url = "https://api.z.ai/api/coding/paas/v4"` (see
|
|
95
|
+
below).
|
|
96
|
+
|
|
97
|
+
Built-in Kimi models include `kimi-k3` (1M context, pay-per-token), and for
|
|
98
|
+
Kimi For Coding subscriptions: `k3` (1M context), `kimi-for-coding`, and
|
|
99
|
+
`kimi-for-coding-highspeed` (256K context). The two use different endpoints
|
|
100
|
+
and keys — a `sk-kimi-*` Kimi For Coding key only works with the
|
|
101
|
+
`kimi-coding` models (`k3`, ...) and vice versa.
|
|
102
|
+
|
|
103
|
+
Switching to a catalog model only requires changing `model` — the provider's
|
|
104
|
+
endpoint and key env var are picked up automatically:
|
|
105
|
+
|
|
106
|
+
```toml
|
|
107
|
+
[llm]
|
|
108
|
+
model = "k3" # Kimi For Coding; base_url/api_key resolve from the catalog
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The `anthropic-messages` dialect is served by a dedicated client
|
|
112
|
+
(`src/limbo/llm/anthropic_client.py`, plain httpx SSE) selected by the
|
|
113
|
+
client factory. It converts OpenAI-style tool definitions to Anthropic's
|
|
114
|
+
shape, merges consecutive tool results into a single user turn, and replays
|
|
115
|
+
assistant thinking blocks with their signature.
|
|
116
|
+
|
|
117
|
+
For the mainland-China Moonshot endpoint, set `base_url =
|
|
118
|
+
"https://api.moonshot.cn/v1"` explicitly (a configured `base_url` always
|
|
119
|
+
wins over the catalog — unless a `[providers.<id>]` override says otherwise,
|
|
120
|
+
see below).
|
|
121
|
+
|
|
122
|
+
### Per-provider overrides
|
|
123
|
+
|
|
124
|
+
An optional `[providers.<id>]` section overrides a single catalog provider
|
|
125
|
+
without touching the global `[llm]` settings — e.g. pointing a provider at a
|
|
126
|
+
relay, renaming its credential env var, or adding extra headers:
|
|
127
|
+
|
|
128
|
+
```toml
|
|
129
|
+
[providers.glm]
|
|
130
|
+
base_url = "https://api.z.ai/api/coding/paas/v4" # international endpoint
|
|
131
|
+
|
|
132
|
+
[providers.codex]
|
|
133
|
+
base_url = "https://my-relay.example.com/v1"
|
|
134
|
+
api_key_env = "CODEX_API_KEY" # rename the env var read for the key
|
|
135
|
+
headers = { x-relay = "on" } # extra headers on every request
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
All fields are optional; unset fields fall back to the catalog. Resolution
|
|
139
|
+
order for `base_url` (first hit wins):
|
|
140
|
+
|
|
141
|
+
1. `[providers.<id>] base_url`
|
|
142
|
+
2. `[llm] base_url` (when changed from the DeepSeek default)
|
|
143
|
+
3. the catalog provider's built-in endpoint
|
|
144
|
+
|
|
145
|
+
and for the API key: `[providers.<id>] api_key` → `[llm] api_key` → the
|
|
146
|
+
environment variable (`[providers.<id>] api_key_env` rename, else the
|
|
147
|
+
catalog's default env var).
|
|
148
|
+
|
|
149
|
+
> **Note:** rule 1 is the single exception to the long-standing "an explicit
|
|
150
|
+
> `[llm] base_url` always wins" behavior — a per-provider override is more
|
|
151
|
+
> specific than the global setting. If you configure both, the
|
|
152
|
+
> `[providers.<id>]` value is used for that provider's models.
|
|
153
|
+
|
|
154
|
+
### Optional LLM settings
|
|
155
|
+
|
|
156
|
+
```toml
|
|
157
|
+
[llm]
|
|
158
|
+
temperature = 0.2 # 0.0 - 2.0, default 0.2
|
|
159
|
+
max_iterations = 50 # safety limit on tool-turn loops, default 50
|
|
160
|
+
max_tokens = 8192 # output cap; default = the model's catalog value
|
|
161
|
+
thinking_effort = "high" # reasoning control; default = provider behavior
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`thinking_effort` is interpreted per model dialect:
|
|
165
|
+
|
|
166
|
+
- `k3`, `kimi-for-coding*` (Anthropic adaptive thinking): `low` | `high` |
|
|
167
|
+
`max` → `thinking: {type: adaptive}` + `output_config.effort`. Thinking
|
|
168
|
+
cannot be disabled; temperature is omitted while thinking is enabled.
|
|
169
|
+
- `kimi-k3` (moonshotai, OpenAI-style): `low` | `high` | `max` → sent as
|
|
170
|
+
`reasoning_effort`. Thinking cannot be disabled on K3.
|
|
171
|
+
- `kimi-k2-thinking`, `kimi-k2.5`+ (DeepSeek-style): any non-`off` value →
|
|
172
|
+
`thinking: {type: enabled}`; `"off"` → `thinking: {type: disabled}`
|
|
173
|
+
(except `kimi-k2.7-code*`, where thinking is always on).
|
|
174
|
+
- `glm-*` (z.ai-style): like DeepSeek-style but with `clear_thinking: false`
|
|
175
|
+
so thinking is preserved across turns; `glm-5.2` additionally maps
|
|
176
|
+
`low`/`high`/`max` to `reasoning_effort` (`low` clamps to `high`).
|
|
177
|
+
- `gpt-5.*` codex models (Responses API): `low` | `medium` | `high` |
|
|
178
|
+
`xhigh` → `reasoning: {effort, summary: auto}`; 5.6-generation models
|
|
179
|
+
(`gpt-5.6-*`) also accept `max`. Temperature is omitted for reasoning
|
|
180
|
+
models.
|
|
181
|
+
- Non-reasoning models: ignored.
|
|
182
|
+
|
|
183
|
+
Reasoning output streams into the chat as muted thinking blocks and is
|
|
184
|
+
stored on the assistant message so it can be replayed to APIs that require
|
|
185
|
+
it (Kimi K3 rejects tool-call replays without `reasoning_content`).
|
|
186
|
+
|
|
187
|
+
Optional tool settings:
|
|
188
|
+
|
|
189
|
+
```toml
|
|
190
|
+
[tools]
|
|
191
|
+
bash_enabled = true
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Session storage
|
|
195
|
+
|
|
196
|
+
Conversations are saved as JSONL files in `~/.limbo/sessions/` so you can
|
|
197
|
+
review or debug them later. Use the `--session-dir` argument to redirect them
|
|
198
|
+
to another location.
|
|
199
|
+
|
|
200
|
+
Old session files are not automatically cleaned up; remove them manually when
|
|
201
|
+
you no longer need them.
|
|
202
|
+
|
|
203
|
+
## Run
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
limbo --workdir /path/to/project
|
|
207
|
+
limbo --model glm-4.7 # override the configured model for this run
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Switching models at runtime
|
|
211
|
+
|
|
212
|
+
Use `/model` in the TUI: without an argument it opens a picker that lists
|
|
213
|
+
catalog models grouped by provider (context window, reasoning capability,
|
|
214
|
+
and the current model are annotated; providers without a resolvable API key
|
|
215
|
+
are dimmed with a hint). `/model <name>` switches directly — unknown names
|
|
216
|
+
fall back to generic OpenAI-compatible defaults. The picker re-reads
|
|
217
|
+
`config.toml` every time it opens, so edits (e.g. a newly added
|
|
218
|
+
`[providers.<id>]`) apply without a restart.
|
|
219
|
+
|
|
220
|
+
A switch takes effect immediately (no restart): the LLM client is rebuilt,
|
|
221
|
+
and the new model is written back to `config.toml` (comments preserved via
|
|
222
|
+
tomlkit) so the next launch keeps it. If the write fails, the switch still
|
|
223
|
+
applies for the current session. Switching is refused while a turn is in
|
|
224
|
+
flight.
|
|
225
|
+
|
|
226
|
+
## Safety
|
|
227
|
+
|
|
228
|
+
Limbo executes every tool call immediately, without asking for confirmation.
|
|
229
|
+
File tools (`read`, `edit`, `write`, `grep`, `find`, `ls`) are bounded to the
|
|
230
|
+
current working directory and reject paths that escape it, including via
|
|
231
|
+
symlinks. The boundary check resolves the path before each operation, so a
|
|
232
|
+
symlink swapped between the check and the operation (a time-of-check-to-time-of-use
|
|
233
|
+
race) could escape the workdir. **This is a known limitation for the MVP.**
|
|
234
|
+
|
|
235
|
+
Bash is an exception: it is started in the working directory but is **not**
|
|
236
|
+
sandboxed. Commands can `cd ..`, use absolute paths, and read or write outside
|
|
237
|
+
the workdir. In addition, commands that match dangerous patterns such as `rm`
|
|
238
|
+
or `git reset --hard` are **rejected outright**.
|
|
239
|
+
The pattern list is configurable but cannot be disabled from the UI. Bash
|
|
240
|
+
commands are filtered with a simple heuristic, but that filter can be bypassed
|
|
241
|
+
by subshells (`bash -c 'rm -rf /'`), command substitution (`$(rm -rf /)`),
|
|
242
|
+
variable indirection, options before the command name
|
|
243
|
+
(`git -C /foo reset --hard`), variable assignments before the command name
|
|
244
|
+
(`VAR=1 rm -rf /`), and similar shell constructs. Only run Limbo with
|
|
245
|
+
trusted commands and in repositories you can afford to modify or lose.
|
|
246
|
+
|
|
247
|
+
If you need to work with untrusted projects, disable the bash tool entirely:
|
|
248
|
+
|
|
249
|
+
```toml
|
|
250
|
+
[tools]
|
|
251
|
+
bash_enabled = false
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Development
|
|
255
|
+
|
|
256
|
+
Run tests:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
pytest tests/ -v
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Run linting and type checks:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
ruff check src tests
|
|
266
|
+
mypy src
|
|
267
|
+
```
|
|
@@ -37,6 +37,40 @@ Built-in providers:
|
|
|
37
37
|
| `deepseek` | openai-completions | `https://api.deepseek.com/v1` | `DEEPSEEK_API_KEY` |
|
|
38
38
|
| `moonshotai` (Kimi) | openai-completions | `https://api.moonshot.ai/v1` | `MOONSHOT_API_KEY` |
|
|
39
39
|
| `kimi-coding` (Kimi For Coding) | anthropic-messages | `https://api.kimi.com/coding` | `KIMI_API_KEY` |
|
|
40
|
+
| `glm` (GLM Coding Plan) | openai-completions | `https://open.bigmodel.cn/api/coding/paas/v4` | `ZHIPUAI_API_KEY` |
|
|
41
|
+
| `codex` (OpenAI Codex) | openai-responses | `https://api.openai.com/v1` (override with your relay) | `CODEX_API_KEY` |
|
|
42
|
+
|
|
43
|
+
Built-in Codex models include `gpt-5.5`, `gpt-5.6-sol`/`terra`/`luna`,
|
|
44
|
+
`gpt-5.4`, `gpt-5.4-mini`, and `gpt-5.3-codex-spark`. **`gpt-5.5` is
|
|
45
|
+
verified by live test against the target relay** (its 1M context is
|
|
46
|
+
relay-reported); the other entries mirror pi's built-in catalog and are
|
|
47
|
+
unverified — availability depends on the relay in use, and unknown or
|
|
48
|
+
unsupported models fall back to the generic OpenAI-compatible defaults.
|
|
49
|
+
Codex speaks the
|
|
50
|
+
OpenAI **Responses API** (`POST {base_url}/responses`), served by a
|
|
51
|
+
dedicated client (`src/limbo/llm/responses_client.py`, plain httpx SSE):
|
|
52
|
+
system messages become `instructions`, tool definitions are flattened, and
|
|
53
|
+
reasoning models stream `reasoning` summaries and replay encrypted
|
|
54
|
+
reasoning items across turns. Limbo targets **API-key relays**, not the
|
|
55
|
+
ChatGPT subscription backend (OAuth) — point the provider at your relay
|
|
56
|
+
and make sure its model IDs match the catalog:
|
|
57
|
+
|
|
58
|
+
```toml
|
|
59
|
+
[llm]
|
|
60
|
+
model = "gpt-5.5"
|
|
61
|
+
|
|
62
|
+
[providers.codex]
|
|
63
|
+
base_url = "https://my-relay.example.com/v1"
|
|
64
|
+
api_key_env = "CODEX_API_KEY"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Built-in GLM Coding Plan models: `glm-4.7`, `glm-5.1`, `glm-5.2` (1M
|
|
68
|
+
context), `glm-5-turbo`, `glm-5v-turbo` (vision), and `glm-4.5-air`. The
|
|
69
|
+
Coding Plan is a subscription with its own endpoint and keys — a coding-plan
|
|
70
|
+
key only works on the `/api/coding/paas/v4` endpoints (not the pay-per-token
|
|
71
|
+
`/api/paas/v4` ones) and vice versa. For the international endpoint set
|
|
72
|
+
`[providers.glm] base_url = "https://api.z.ai/api/coding/paas/v4"` (see
|
|
73
|
+
below).
|
|
40
74
|
|
|
41
75
|
Built-in Kimi models include `kimi-k3` (1M context, pay-per-token), and for
|
|
42
76
|
Kimi For Coding subscriptions: `k3` (1M context), `kimi-for-coding`, and
|
|
@@ -60,14 +94,47 @@ assistant thinking blocks with their signature.
|
|
|
60
94
|
|
|
61
95
|
For the mainland-China Moonshot endpoint, set `base_url =
|
|
62
96
|
"https://api.moonshot.cn/v1"` explicitly (a configured `base_url` always
|
|
63
|
-
wins over the catalog
|
|
97
|
+
wins over the catalog — unless a `[providers.<id>]` override says otherwise,
|
|
98
|
+
see below).
|
|
99
|
+
|
|
100
|
+
### Per-provider overrides
|
|
101
|
+
|
|
102
|
+
An optional `[providers.<id>]` section overrides a single catalog provider
|
|
103
|
+
without touching the global `[llm]` settings — e.g. pointing a provider at a
|
|
104
|
+
relay, renaming its credential env var, or adding extra headers:
|
|
105
|
+
|
|
106
|
+
```toml
|
|
107
|
+
[providers.glm]
|
|
108
|
+
base_url = "https://api.z.ai/api/coding/paas/v4" # international endpoint
|
|
109
|
+
|
|
110
|
+
[providers.codex]
|
|
111
|
+
base_url = "https://my-relay.example.com/v1"
|
|
112
|
+
api_key_env = "CODEX_API_KEY" # rename the env var read for the key
|
|
113
|
+
headers = { x-relay = "on" } # extra headers on every request
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
All fields are optional; unset fields fall back to the catalog. Resolution
|
|
117
|
+
order for `base_url` (first hit wins):
|
|
118
|
+
|
|
119
|
+
1. `[providers.<id>] base_url`
|
|
120
|
+
2. `[llm] base_url` (when changed from the DeepSeek default)
|
|
121
|
+
3. the catalog provider's built-in endpoint
|
|
122
|
+
|
|
123
|
+
and for the API key: `[providers.<id>] api_key` → `[llm] api_key` → the
|
|
124
|
+
environment variable (`[providers.<id>] api_key_env` rename, else the
|
|
125
|
+
catalog's default env var).
|
|
126
|
+
|
|
127
|
+
> **Note:** rule 1 is the single exception to the long-standing "an explicit
|
|
128
|
+
> `[llm] base_url` always wins" behavior — a per-provider override is more
|
|
129
|
+
> specific than the global setting. If you configure both, the
|
|
130
|
+
> `[providers.<id>]` value is used for that provider's models.
|
|
64
131
|
|
|
65
132
|
### Optional LLM settings
|
|
66
133
|
|
|
67
134
|
```toml
|
|
68
135
|
[llm]
|
|
69
136
|
temperature = 0.2 # 0.0 - 2.0, default 0.2
|
|
70
|
-
max_iterations =
|
|
137
|
+
max_iterations = 50 # safety limit on tool-turn loops, default 50
|
|
71
138
|
max_tokens = 8192 # output cap; default = the model's catalog value
|
|
72
139
|
thinking_effort = "high" # reasoning control; default = provider behavior
|
|
73
140
|
```
|
|
@@ -82,6 +149,13 @@ thinking_effort = "high" # reasoning control; default = provider behavior
|
|
|
82
149
|
- `kimi-k2-thinking`, `kimi-k2.5`+ (DeepSeek-style): any non-`off` value →
|
|
83
150
|
`thinking: {type: enabled}`; `"off"` → `thinking: {type: disabled}`
|
|
84
151
|
(except `kimi-k2.7-code*`, where thinking is always on).
|
|
152
|
+
- `glm-*` (z.ai-style): like DeepSeek-style but with `clear_thinking: false`
|
|
153
|
+
so thinking is preserved across turns; `glm-5.2` additionally maps
|
|
154
|
+
`low`/`high`/`max` to `reasoning_effort` (`low` clamps to `high`).
|
|
155
|
+
- `gpt-5.*` codex models (Responses API): `low` | `medium` | `high` |
|
|
156
|
+
`xhigh` → `reasoning: {effort, summary: auto}`; 5.6-generation models
|
|
157
|
+
(`gpt-5.6-*`) also accept `max`. Temperature is omitted for reasoning
|
|
158
|
+
models.
|
|
85
159
|
- Non-reasoning models: ignored.
|
|
86
160
|
|
|
87
161
|
Reasoning output streams into the chat as muted thinking blocks and is
|
|
@@ -108,8 +182,25 @@ you no longer need them.
|
|
|
108
182
|
|
|
109
183
|
```bash
|
|
110
184
|
limbo --workdir /path/to/project
|
|
185
|
+
limbo --model glm-4.7 # override the configured model for this run
|
|
111
186
|
```
|
|
112
187
|
|
|
188
|
+
### Switching models at runtime
|
|
189
|
+
|
|
190
|
+
Use `/model` in the TUI: without an argument it opens a picker that lists
|
|
191
|
+
catalog models grouped by provider (context window, reasoning capability,
|
|
192
|
+
and the current model are annotated; providers without a resolvable API key
|
|
193
|
+
are dimmed with a hint). `/model <name>` switches directly — unknown names
|
|
194
|
+
fall back to generic OpenAI-compatible defaults. The picker re-reads
|
|
195
|
+
`config.toml` every time it opens, so edits (e.g. a newly added
|
|
196
|
+
`[providers.<id>]`) apply without a restart.
|
|
197
|
+
|
|
198
|
+
A switch takes effect immediately (no restart): the LLM client is rebuilt,
|
|
199
|
+
and the new model is written back to `config.toml` (comments preserved via
|
|
200
|
+
tomlkit) so the next launch keeps it. If the write fails, the switch still
|
|
201
|
+
applies for the current session. Switching is refused while a turn is in
|
|
202
|
+
flight.
|
|
203
|
+
|
|
113
204
|
## Safety
|
|
114
205
|
|
|
115
206
|
Limbo executes every tool call immediately, without asking for confirmation.
|