dostuff 0.2.2__tar.gz → 0.2.3__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.
- {dostuff-0.2.2 → dostuff-0.2.3}/PKG-INFO +4 -2
- {dostuff-0.2.2 → dostuff-0.2.3}/README.md +3 -1
- dostuff-0.2.3/dostuff/helpers/agent/get_model_token_limit.py +54 -0
- dostuff-0.2.3/dostuff/helpers/models/opencode.py +200 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/templates/config.example.yaml +1 -1
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff.egg-info/PKG-INFO +4 -2
- {dostuff-0.2.2 → dostuff-0.2.3}/pyproject.toml +1 -1
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_opencode.py +43 -0
- dostuff-0.2.2/dostuff/helpers/agent/get_model_token_limit.py +0 -29
- dostuff-0.2.2/dostuff/helpers/models/opencode.py +0 -76
- {dostuff-0.2.2 → dostuff-0.2.3}/LICENSE +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/__init__.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/__main__.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/agent/call_agent.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/agent/instructions.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/agent/loop.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/agent/permissions.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/agent/run_tool.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/cli.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/cli_tui.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/config.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/headless.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/agent/append_step.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/agent/constants.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/agent/consume_stream.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/agent/context_usage.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/agent/environment_context.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/agent/file_mentions.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/agent/load_identity.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/agent/load_project_instructions.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/agent/loop_check_cancelled.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/agent/loop_emit.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/agent/manage_context.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/agent/model_cost.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/agent/pastes.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/agent/quick_memory.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/agent/slash_suggestion.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/agent/tool_output.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/mcp/load_mcp_config.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/mcp/mcp_oauth.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/mcp/server_config.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/mcp/token_store.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/memory/export_session.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/memory/resolve_memory_operation.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/memory/shell_step.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/models/add_model_entry.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/models/list_models.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/skills/custom_commands.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/skills/discover_skills.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/skills/skill_command.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/tools/diff.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/tools/generate_tool_schema.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/tools/project_root.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/tools/resolve_safe_path.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/ui/__init__.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/ui/doctor.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/ui/emit.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/ui/prompt_history.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/helpers/ui/terminal_signals.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/lib/checkpoints.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/lib/exceptions.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/lib/mcp/mcp_client.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/lib/mcp/mcp_client_registration_store.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/lib/mcp/mcp_tool_registry_store.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/lib/memory/chroma.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/lib/memory/episodic_memory_store.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/lib/memory/semantic_memory_store.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/lib/memory/session_store.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/lib/memory/stores.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/lib/memory/types.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/lib/model.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/lib/tracing.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/lib/worktree.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/memory/__init__.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/setup_wizard.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/skills.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/templates/env.example +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/__init__.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/bash/bash_command.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/definitions.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/delegate_to_subagent.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/files/delete_file.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/files/edit_file.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/files/list_files.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/files/read_file.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/files/write_file.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/get_current_datetime.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/mcp/call_mcp_tool.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/mcp/get_mcp_tool_details.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/mcp/search_mcp_tools.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/memory/save_episode.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/memory/save_memory.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/search/glob.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/search/grep.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/search/walk.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/settings.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/todo_write.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/web_fetch.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff/tools/web_search.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff.egg-info/SOURCES.txt +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff.egg-info/dependency_links.txt +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff.egg-info/entry_points.txt +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff.egg-info/requires.txt +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/dostuff.egg-info/top_level.txt +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/setup.cfg +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_checkpoints_todos_caching.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_cli_commands.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_config_memory_mcp.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_file_mentions.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_file_tools.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_loop.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_mcp_e2e.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_mcp_oauth.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_optional_memory_and_instructions.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_pastes.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_permissions.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_roadmap_021.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_session_store.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_settings_tools.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_setup_wizard.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_skill_command.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_slash_suggestion.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_subagent.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_terminal_signals.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_tool_schemas.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_tui.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_web_fetch.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_web_search.py +0 -0
- {dostuff-0.2.2 → dostuff-0.2.3}/tests/test_worktree.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dostuff
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: DoStuff - a terminal coding agent for any LLM: TUI and headless mode, permissions, MCP, skills, web tools, sessions and git worktrees
|
|
5
5
|
Author-email: Varun Kumawat <varunkumawatleap2@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -352,7 +352,7 @@ models:
|
|
|
352
352
|
loop:
|
|
353
353
|
max_iterations: 100
|
|
354
354
|
subagent_max_iterations: 25
|
|
355
|
-
# context_window: 128000 # override when litellm
|
|
355
|
+
# context_window: 128000 # override when dostuff/litellm don't know the model
|
|
356
356
|
max_tool_output_chars: 30000 # longer tool results keep head + tail
|
|
357
357
|
max_retries: 6 # retries on rate limits, timeouts, 5xx (0 = none)
|
|
358
358
|
retry_max_delay: 60 # cap on each wait, seconds; Retry-After is honored
|
|
@@ -416,6 +416,8 @@ or `/model` inside the TUI. Model lists come from litellm's catalog (chat models
|
|
|
416
416
|
|
|
417
417
|
OpenCode models (`opencode/<id>` for Zen, `opencode-go/<id>` for a Go subscription) use the ids from the [OpenCode docs](https://opencode.ai/docs/zen/). litellm has no OpenCode provider, so DoStuff sends each model to the gateway endpoint for its family: Claude models (and a few Qwen / MiniMax models) to the Anthropic Messages endpoint, GPT / Grok / Muse models to the Responses endpoint, Gemini models to the Gemini endpoint, and all other models to chat completions. One console key in `OPENCODE_API_KEY` works for Zen and Go. OpenCode only allows its free models from its own app, so use paid models or Go.
|
|
418
418
|
|
|
419
|
+
The gateways don't publish a model's context window in `/v1/models`, so `helpers/models/opencode.py` carries one per model, taken from [models.dev](https://models.dev)'s `opencode` and `opencode-go` entries (the same id can differ per gateway: `opencode/minimax-m3` is 512k, `opencode-go/minimax-m3` is 1M). `/context`, the status bar and auto-compaction size themselves from it; set `loop.context_window` to override.
|
|
420
|
+
|
|
419
421
|
---
|
|
420
422
|
|
|
421
423
|
## MCP servers
|
|
@@ -304,7 +304,7 @@ models:
|
|
|
304
304
|
loop:
|
|
305
305
|
max_iterations: 100
|
|
306
306
|
subagent_max_iterations: 25
|
|
307
|
-
# context_window: 128000 # override when litellm
|
|
307
|
+
# context_window: 128000 # override when dostuff/litellm don't know the model
|
|
308
308
|
max_tool_output_chars: 30000 # longer tool results keep head + tail
|
|
309
309
|
max_retries: 6 # retries on rate limits, timeouts, 5xx (0 = none)
|
|
310
310
|
retry_max_delay: 60 # cap on each wait, seconds; Retry-After is honored
|
|
@@ -368,6 +368,8 @@ or `/model` inside the TUI. Model lists come from litellm's catalog (chat models
|
|
|
368
368
|
|
|
369
369
|
OpenCode models (`opencode/<id>` for Zen, `opencode-go/<id>` for a Go subscription) use the ids from the [OpenCode docs](https://opencode.ai/docs/zen/). litellm has no OpenCode provider, so DoStuff sends each model to the gateway endpoint for its family: Claude models (and a few Qwen / MiniMax models) to the Anthropic Messages endpoint, GPT / Grok / Muse models to the Responses endpoint, Gemini models to the Gemini endpoint, and all other models to chat completions. One console key in `OPENCODE_API_KEY` works for Zen and Go. OpenCode only allows its free models from its own app, so use paid models or Go.
|
|
370
370
|
|
|
371
|
+
The gateways don't publish a model's context window in `/v1/models`, so `helpers/models/opencode.py` carries one per model, taken from [models.dev](https://models.dev)'s `opencode` and `opencode-go` entries (the same id can differ per gateway: `opencode/minimax-m3` is 512k, `opencode-go/minimax-m3` is 1M). `/context`, the status bar and auto-compaction size themselves from it; set `loop.context_window` to override.
|
|
372
|
+
|
|
371
373
|
---
|
|
372
374
|
|
|
373
375
|
## MCP servers
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import litellm
|
|
2
|
+
from dostuff.config import get_config
|
|
3
|
+
from dostuff.helpers.models.opencode import context_window, is_opencode
|
|
4
|
+
from dostuff.lib.model import get_litellm_model, get_model
|
|
5
|
+
|
|
6
|
+
FALLBACK_CONTEXT_WINDOW = 128_000
|
|
7
|
+
_warned_models: set[str] = set()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _litellm_limit(model: str) -> int | None:
|
|
11
|
+
"""litellm's `max_input_tokens` for `model`, or None when it doesn't map it."""
|
|
12
|
+
try:
|
|
13
|
+
limit = litellm.get_model_info(model=model).get("max_input_tokens")
|
|
14
|
+
except Exception:
|
|
15
|
+
return None
|
|
16
|
+
return int(limit) if limit else None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_model_token_limit() -> int:
|
|
20
|
+
"""Context window for the active model: `loop.context_window`, else the OpenCode table, else
|
|
21
|
+
litellm's model info, else 128k.
|
|
22
|
+
|
|
23
|
+
OpenCode ids are checked against `helpers/models/opencode._CONTEXT_WINDOWS` first: litellm only
|
|
24
|
+
knows their upstream `openai/` / `anthropic/` route for a few of them, and the same id can have a
|
|
25
|
+
different window on each gateway.
|
|
26
|
+
"""
|
|
27
|
+
configured = get_config().context_window
|
|
28
|
+
if configured > 0:
|
|
29
|
+
return configured
|
|
30
|
+
opencode = is_opencode(get_config().provider)
|
|
31
|
+
if opencode:
|
|
32
|
+
model_id = get_model().split("/", 1)[-1]
|
|
33
|
+
known = context_window(get_config().provider, model_id)
|
|
34
|
+
if known:
|
|
35
|
+
return known
|
|
36
|
+
model = get_litellm_model()
|
|
37
|
+
limit = _litellm_limit(model)
|
|
38
|
+
if limit:
|
|
39
|
+
return limit
|
|
40
|
+
if opencode:
|
|
41
|
+
# litellm keys some ids only without the route prefix, e.g. `deepseek-v4-pro` (1M) exists
|
|
42
|
+
# while `openai/deepseek-v4-pro` doesn't.
|
|
43
|
+
limit = _litellm_limit(get_model().split("/", 1)[-1])
|
|
44
|
+
if limit:
|
|
45
|
+
return limit
|
|
46
|
+
if model not in _warned_models:
|
|
47
|
+
_warned_models.add(model)
|
|
48
|
+
from dostuff.helpers.ui.emit import emit
|
|
49
|
+
emit(
|
|
50
|
+
f"Unknown context window for '{model}'; assuming {FALLBACK_CONTEXT_WINDOW:,} tokens. "
|
|
51
|
+
"Set loop.context_window in config.yaml to override.",
|
|
52
|
+
msg_type="system",
|
|
53
|
+
)
|
|
54
|
+
return FALLBACK_CONTEXT_WINDOW
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""OpenCode Zen (`opencode/<id>`) and OpenCode Go (`opencode-go/<id>`) models.
|
|
2
|
+
|
|
3
|
+
litellm has no OpenCode provider. Each gateway serves a model family on the endpoint of its
|
|
4
|
+
upstream API (https://opencode.ai/docs/zen/), so a model is sent through litellm's openai,
|
|
5
|
+
anthropic or gemini route with the gateway as api_base.
|
|
6
|
+
"""
|
|
7
|
+
import os
|
|
8
|
+
from typing import Any, Literal
|
|
9
|
+
|
|
10
|
+
OPENCODE_PROVIDERS = {
|
|
11
|
+
"opencode": "https://opencode.ai/inference",
|
|
12
|
+
"opencode-go": "https://opencode.ai/inference/go",
|
|
13
|
+
}
|
|
14
|
+
OPENCODE_API_KEY_ENV = "OPENCODE_API_KEY" # one console key works for Zen and Go
|
|
15
|
+
# Served on the Anthropic Messages endpoint besides Claude; they differ between the gateways
|
|
16
|
+
_ANTHROPIC_IDS = {
|
|
17
|
+
"opencode": {"qwen3.8-flash", "qwen3.6-plus", "qwen3.5-plus"},
|
|
18
|
+
"opencode-go": {"qwen3.8-max", "qwen3.8-flash", "qwen3.7-plus", "minimax-m3"},
|
|
19
|
+
}
|
|
20
|
+
# Input-token limit per model, from models.dev's `opencode` / `opencode-go` entries
|
|
21
|
+
# (https://models.dev — the catalog OpenCode's own clients size their context with). The gateways
|
|
22
|
+
# don't publish limits in /v1/models, litellm doesn't map their ids, and the same model can have a
|
|
23
|
+
# different window on each gateway, so this table is the only reliable source. Refresh it when
|
|
24
|
+
# `provider_models()` lists an id that is missing here (those fall back to litellm, then 128k).
|
|
25
|
+
_CONTEXT_WINDOWS = {
|
|
26
|
+
"opencode": {
|
|
27
|
+
"beam-free": 1_048_576,
|
|
28
|
+
"big-pickle": 160_000,
|
|
29
|
+
"claude-fable-5": 1_000_000,
|
|
30
|
+
"claude-fable-5-1": 1_000_000,
|
|
31
|
+
"claude-haiku-4-5": 200_000,
|
|
32
|
+
"claude-haiku-5-5": 1_000_000,
|
|
33
|
+
"claude-opus-4-5": 200_000,
|
|
34
|
+
"claude-opus-4-6": 1_000_000,
|
|
35
|
+
"claude-opus-4-7": 1_000_000,
|
|
36
|
+
"claude-opus-4-8": 1_000_000,
|
|
37
|
+
"claude-opus-5": 1_000_000,
|
|
38
|
+
"claude-opus-5-5": 1_000_000,
|
|
39
|
+
"claude-sonnet-4-5": 1_000_000,
|
|
40
|
+
"claude-sonnet-4-6": 1_000_000,
|
|
41
|
+
"claude-sonnet-5": 1_000_000,
|
|
42
|
+
"claude-sonnet-5-5": 1_000_000,
|
|
43
|
+
"deepseek-v4-pro": 1_000_000,
|
|
44
|
+
"deepseek-v4.1-flash": 1_000_000,
|
|
45
|
+
"deepseek-v4.1-flash-free": 200_000,
|
|
46
|
+
"flint-40b-free": 262_144,
|
|
47
|
+
"gemini-3-flash": 1_048_576,
|
|
48
|
+
"gemini-3.1-pro": 1_048_576,
|
|
49
|
+
"gemini-3.5-flash": 1_048_576,
|
|
50
|
+
"gemini-3.5-flash-lite": 1_048_576,
|
|
51
|
+
"gemini-3.6-flash": 1_048_576,
|
|
52
|
+
"gemini-3.7-flash": 1_048_576,
|
|
53
|
+
"gemini-3.8-flash": 1_048_576,
|
|
54
|
+
"gemini-4-argon": 1_048_576,
|
|
55
|
+
"glm-5": 204_800,
|
|
56
|
+
"glm-5.1": 204_800,
|
|
57
|
+
"glm-5.2": 1_000_000,
|
|
58
|
+
"glm-5.3": 1_000_000,
|
|
59
|
+
"glm-5.3-flash": 1_000_000,
|
|
60
|
+
"glm-5.3-flash-free": 200_000,
|
|
61
|
+
"gpt-5": 272_000,
|
|
62
|
+
"gpt-5-codex": 272_000,
|
|
63
|
+
"gpt-5-nano": 272_000,
|
|
64
|
+
"gpt-5.1": 272_000,
|
|
65
|
+
"gpt-5.1-codex": 272_000,
|
|
66
|
+
"gpt-5.1-codex-max": 272_000,
|
|
67
|
+
"gpt-5.1-codex-mini": 272_000,
|
|
68
|
+
"gpt-5.2": 272_000,
|
|
69
|
+
"gpt-5.2-codex": 272_000,
|
|
70
|
+
"gpt-5.3-codex": 272_000,
|
|
71
|
+
"gpt-5.3-codex-spark": 128_000,
|
|
72
|
+
"gpt-5.4": 922_000,
|
|
73
|
+
"gpt-5.4-mini": 272_000,
|
|
74
|
+
"gpt-5.4-nano": 272_000,
|
|
75
|
+
"gpt-5.4-pro": 922_000,
|
|
76
|
+
"gpt-5.5": 922_000,
|
|
77
|
+
"gpt-5.5-pro": 922_000,
|
|
78
|
+
"gpt-5.6-luna": 922_000,
|
|
79
|
+
"gpt-5.6-sol": 922_000,
|
|
80
|
+
"gpt-5.6-terra": 922_000,
|
|
81
|
+
"gpt-6-astra": 922_000,
|
|
82
|
+
"gpt-6-luna": 922_000,
|
|
83
|
+
"gpt-6-sol": 922_000,
|
|
84
|
+
"gpt-6.1-sol": 922_000,
|
|
85
|
+
"grok-4.5": 500_000,
|
|
86
|
+
"grok-4.6": 500_000,
|
|
87
|
+
"grok-4.7": 500_000,
|
|
88
|
+
"grok-build-0.1": 256_000,
|
|
89
|
+
"kimi-k2.5": 262_144,
|
|
90
|
+
"kimi-k2.6": 262_144,
|
|
91
|
+
"kimi-k2.7-code": 262_144,
|
|
92
|
+
"kimi-k3": 1_048_576,
|
|
93
|
+
"mimo-v2.6-flash-free": 200_000,
|
|
94
|
+
"minimax-m2.5": 204_800,
|
|
95
|
+
"minimax-m2.7": 204_800,
|
|
96
|
+
"minimax-m3": 512_000,
|
|
97
|
+
"mistral-large-4": 524_288,
|
|
98
|
+
"muse-spark-1.2": 1_048_576,
|
|
99
|
+
"muse-spark-1.3": 1_048_576,
|
|
100
|
+
"muse-wonder-1.0": 1_048_576,
|
|
101
|
+
"nemotron-3.5-lightning-free": 262_144,
|
|
102
|
+
"nemotron-3.5-super-vl-free": 1_048_576,
|
|
103
|
+
"qwen3.5-plus": 262_144,
|
|
104
|
+
"qwen3.6-plus": 262_144,
|
|
105
|
+
"qwen3.8-flash": 1_000_000,
|
|
106
|
+
"qwen3.8-max": 262_144,
|
|
107
|
+
},
|
|
108
|
+
"opencode-go": {
|
|
109
|
+
"beam-free": 1_048_576,
|
|
110
|
+
"claude-haiku-5-5": 1_000_000,
|
|
111
|
+
"deepseek-v4-pro": 1_000_000,
|
|
112
|
+
"deepseek-v4.1-flash": 1_000_000,
|
|
113
|
+
"gemini-4-argon": 1_048_576,
|
|
114
|
+
"glm-5.2": 1_000_000,
|
|
115
|
+
"glm-5.3": 1_000_000,
|
|
116
|
+
"glm-5.3-flash": 1_000_000,
|
|
117
|
+
"gpt-5.6-luna": 922_000,
|
|
118
|
+
"gpt-6-luna": 922_000,
|
|
119
|
+
"grok-4.7": 500_000,
|
|
120
|
+
"hy3": 192_000,
|
|
121
|
+
"hy4-preview": 1_024_000,
|
|
122
|
+
"iceman-contributor-free": 262_144,
|
|
123
|
+
"kimi-k2.7-code": 262_144,
|
|
124
|
+
"kimi-k3": 1_048_576,
|
|
125
|
+
"kimi-k3.1-pro": 1_048_576,
|
|
126
|
+
"longcat-2.0": 1_000_000,
|
|
127
|
+
"mimo-v2.6-flash": 1_048_576,
|
|
128
|
+
"mimo-v2.6-pro": 1_048_576,
|
|
129
|
+
"minimax-m3": 1_000_000,
|
|
130
|
+
"minimax-m3.1-flash": 1_000_000,
|
|
131
|
+
"muse-spark-1.2-contributor": 1_048_576,
|
|
132
|
+
"muse-spark-1.3-contributor": 1_048_576,
|
|
133
|
+
"qwen3.7-plus": 1_000_000,
|
|
134
|
+
"qwen3.8-flash": 1_000_000,
|
|
135
|
+
"qwen3.8-max": 1_000_000,
|
|
136
|
+
"step-5-preview": 1_000_000,
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
# In /v1/models but not chat models
|
|
140
|
+
_NOT_CHAT = ("jev-", "test")
|
|
141
|
+
|
|
142
|
+
Endpoint = Literal["chat", "responses", "anthropic", "google"]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def is_opencode(provider: str) -> bool:
|
|
146
|
+
return provider in OPENCODE_PROVIDERS
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def context_window(provider: str, model_id: str) -> int | None:
|
|
150
|
+
"""Input-token limit for an OpenCode model id, or None if the table doesn't list it."""
|
|
151
|
+
return _CONTEXT_WINDOWS.get(provider, {}).get(model_id)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def endpoint_for(provider: str, model_id: str) -> Endpoint:
|
|
155
|
+
"""Which upstream API the gateway serves `model_id` on."""
|
|
156
|
+
if model_id.startswith("claude-") or model_id in _ANTHROPIC_IDS[provider]:
|
|
157
|
+
return "anthropic"
|
|
158
|
+
if model_id.startswith(("gpt-", "grok-", "muse-")):
|
|
159
|
+
return "responses"
|
|
160
|
+
if model_id.startswith("gemini-"):
|
|
161
|
+
return "google"
|
|
162
|
+
return "chat"
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def litellm_route(provider: str, model_id: str) -> tuple[str, str]:
|
|
166
|
+
"""(litellm model, api_base) for an OpenCode model id."""
|
|
167
|
+
base = OPENCODE_PROVIDERS[provider]
|
|
168
|
+
endpoint = endpoint_for(provider, model_id)
|
|
169
|
+
if endpoint == "anthropic":
|
|
170
|
+
return f"anthropic/{model_id}", f"{base}/anthropic" # litellm appends /v1/messages
|
|
171
|
+
if endpoint == "google":
|
|
172
|
+
return f"gemini/{model_id}", f"{base}/google/v1beta"
|
|
173
|
+
if endpoint == "responses":
|
|
174
|
+
return f"openai/responses/{model_id}", f"{base}/openai/v1"
|
|
175
|
+
return f"openai/{model_id}", f"{base}/openai/v1"
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def opencode_kwargs(provider: str, model_id: str, api_key: str | None, session_id: str, version: str) -> dict[str, Any]:
|
|
179
|
+
"""litellm kwargs for an OpenCode model. Go asks clients to name themselves and send a
|
|
180
|
+
stable per-conversation id; Zen gets the same headers."""
|
|
181
|
+
model, api_base = litellm_route(provider, model_id)
|
|
182
|
+
return {
|
|
183
|
+
"model": model,
|
|
184
|
+
"api_base": api_base,
|
|
185
|
+
# Never fall back to OPENAI_API_KEY / ANTHROPIC_API_KEY: those would go to opencode.ai
|
|
186
|
+
"api_key": api_key or os.environ.get(OPENCODE_API_KEY_ENV) or "missing-opencode-key",
|
|
187
|
+
"extra_headers": {"User-Agent": f"dostuff/{version}", "x-opencode-session": session_id},
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def models_url(provider: str) -> str:
|
|
192
|
+
return f"{OPENCODE_PROVIDERS[provider]}/v1/models"
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def chat_model_ids(data: dict) -> list[str]:
|
|
196
|
+
"""Model ids from a /v1/models response, without the non-chat ones."""
|
|
197
|
+
return [
|
|
198
|
+
m["id"] for m in data.get("data") or []
|
|
199
|
+
if isinstance(m, dict) and m.get("id") and not str(m["id"]).startswith(_NOT_CHAT)
|
|
200
|
+
]
|
|
@@ -17,7 +17,7 @@ loop:
|
|
|
17
17
|
max_iterations: 100 # max agent turns before forced exit; ESC cancels anytime
|
|
18
18
|
subagent_max_iterations: 25 # iteration cap for delegate_to_subagent
|
|
19
19
|
prompt_caching: true # Claude models: cache the system prompt and history between calls (cheaper, faster)
|
|
20
|
-
# context_window: 128000 # override the model's context window (tokens) if litellm
|
|
20
|
+
# context_window: 128000 # override the model's context window (tokens) if dostuff/litellm can't tell
|
|
21
21
|
max_tool_output_chars: 30000 # longer tool results keep head + tail with a truncation marker
|
|
22
22
|
max_retries: 6 # retries on rate limits, timeouts and 5xx (0 = fail at once)
|
|
23
23
|
retry_max_delay: 60 # longest wait between retries, seconds (backoff doubles from 1s; Retry-After is honored)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dostuff
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: DoStuff - a terminal coding agent for any LLM: TUI and headless mode, permissions, MCP, skills, web tools, sessions and git worktrees
|
|
5
5
|
Author-email: Varun Kumawat <varunkumawatleap2@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -352,7 +352,7 @@ models:
|
|
|
352
352
|
loop:
|
|
353
353
|
max_iterations: 100
|
|
354
354
|
subagent_max_iterations: 25
|
|
355
|
-
# context_window: 128000 # override when litellm
|
|
355
|
+
# context_window: 128000 # override when dostuff/litellm don't know the model
|
|
356
356
|
max_tool_output_chars: 30000 # longer tool results keep head + tail
|
|
357
357
|
max_retries: 6 # retries on rate limits, timeouts, 5xx (0 = none)
|
|
358
358
|
retry_max_delay: 60 # cap on each wait, seconds; Retry-After is honored
|
|
@@ -416,6 +416,8 @@ or `/model` inside the TUI. Model lists come from litellm's catalog (chat models
|
|
|
416
416
|
|
|
417
417
|
OpenCode models (`opencode/<id>` for Zen, `opencode-go/<id>` for a Go subscription) use the ids from the [OpenCode docs](https://opencode.ai/docs/zen/). litellm has no OpenCode provider, so DoStuff sends each model to the gateway endpoint for its family: Claude models (and a few Qwen / MiniMax models) to the Anthropic Messages endpoint, GPT / Grok / Muse models to the Responses endpoint, Gemini models to the Gemini endpoint, and all other models to chat completions. One console key in `OPENCODE_API_KEY` works for Zen and Go. OpenCode only allows its free models from its own app, so use paid models or Go.
|
|
418
418
|
|
|
419
|
+
The gateways don't publish a model's context window in `/v1/models`, so `helpers/models/opencode.py` carries one per model, taken from [models.dev](https://models.dev)'s `opencode` and `opencode-go` entries (the same id can differ per gateway: `opencode/minimax-m3` is 512k, `opencode-go/minimax-m3` is 1M). `/context`, the status bar and auto-compaction size themselves from it; set `loop.context_window` to override.
|
|
420
|
+
|
|
419
421
|
---
|
|
420
422
|
|
|
421
423
|
## MCP servers
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dostuff"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.3"
|
|
8
8
|
description = "DoStuff - a terminal coding agent for any LLM: TUI and headless mode, permissions, MCP, skills, web tools, sessions and git worktrees"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""OpenCode Zen / Go models: routing through litellm's openai / anthropic / gemini providers."""
|
|
2
2
|
import pytest
|
|
3
3
|
|
|
4
|
+
import dostuff.helpers.agent.get_model_token_limit as token_limit_module
|
|
4
5
|
from dostuff import config as config_module
|
|
5
6
|
from dostuff.config import reset_config
|
|
6
7
|
from dostuff.helpers.models import list_models
|
|
@@ -79,3 +80,45 @@ def test_model_list(monkeypatch):
|
|
|
79
80
|
assert chat_model_ids(data) == ["kimi-k3", "claude-haiku-5-5"]
|
|
80
81
|
monkeypatch.setattr(list_models, "_get_json", lambda url, what: data if url.endswith("/go/v1/models") else {})
|
|
81
82
|
assert list_models.provider_models("opencode-go") == ["opencode-go/claude-haiku-5-5", "opencode-go/kimi-k3"]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_context_window_table(model):
|
|
86
|
+
"""OpenCode gateways publish no context limit, so the table in helpers/models/opencode.py is used."""
|
|
87
|
+
t = token_limit_module
|
|
88
|
+
for name, window in [
|
|
89
|
+
("opencode-go/deepseek-v4.1-flash", 1_000_000), # the model that used to be assumed 128k
|
|
90
|
+
("opencode/deepseek-v4.1-flash", 1_000_000),
|
|
91
|
+
("opencode/minimax-m3", 512_000), # same id, different window per gateway
|
|
92
|
+
("opencode-go/minimax-m3", 1_000_000),
|
|
93
|
+
("opencode/qwen3.8-max", 262_144),
|
|
94
|
+
("opencode-go/qwen3.8-max", 1_000_000),
|
|
95
|
+
]:
|
|
96
|
+
model(name)
|
|
97
|
+
assert t.get_model_token_limit() == window, name
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_context_window_bare_litellm_fallback(model, monkeypatch):
|
|
101
|
+
"""An id missing from the table still resolves through litellm without the route prefix."""
|
|
102
|
+
t = token_limit_module
|
|
103
|
+
monkeypatch.setattr(t, "context_window", lambda provider, model_id: None)
|
|
104
|
+
model("opencode-go/deepseek-v4-pro")
|
|
105
|
+
assert t._litellm_limit("openai/deepseek-v4-pro") is None # the routed name is unknown to litellm
|
|
106
|
+
assert t._litellm_limit("deepseek-v4-pro") == 1_000_000 # the bare id is not
|
|
107
|
+
assert t.get_model_token_limit() == 1_000_000
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_context_window_configured_wins(model, monkeypatch):
|
|
111
|
+
model("opencode-go/deepseek-v4.1-flash")
|
|
112
|
+
monkeypatch.setattr(token_limit_module.get_config(), "context_window", 32_000)
|
|
113
|
+
assert token_limit_module.get_model_token_limit() == 32_000
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_context_window_unknown_warns_once(model, monkeypatch):
|
|
117
|
+
t = token_limit_module
|
|
118
|
+
emitted = []
|
|
119
|
+
monkeypatch.setattr("dostuff.helpers.ui.emit.emit", lambda message, msg_type="system": emitted.append(message))
|
|
120
|
+
monkeypatch.setattr(t, "_warned_models", set())
|
|
121
|
+
model("opencode-go/not-a-real-model")
|
|
122
|
+
assert t.get_model_token_limit() == t.FALLBACK_CONTEXT_WINDOW
|
|
123
|
+
assert t.get_model_token_limit() == t.FALLBACK_CONTEXT_WINDOW
|
|
124
|
+
assert len(emitted) == 1 and "128,000" in emitted[0]
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import litellm
|
|
2
|
-
from dostuff.config import get_config
|
|
3
|
-
from dostuff.lib.model import get_litellm_model
|
|
4
|
-
|
|
5
|
-
FALLBACK_CONTEXT_WINDOW = 128_000
|
|
6
|
-
_warned_models: set[str] = set()
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def get_model_token_limit() -> int:
|
|
10
|
-
"""Context window for the active model: `loop.context_window`, else litellm's model info, else 128k."""
|
|
11
|
-
configured = get_config().context_window
|
|
12
|
-
if configured > 0:
|
|
13
|
-
return configured
|
|
14
|
-
model = get_litellm_model()
|
|
15
|
-
try:
|
|
16
|
-
limit = litellm.get_model_info(model=model).get("max_input_tokens")
|
|
17
|
-
if limit:
|
|
18
|
-
return int(limit)
|
|
19
|
-
except Exception:
|
|
20
|
-
pass
|
|
21
|
-
if model not in _warned_models:
|
|
22
|
-
_warned_models.add(model)
|
|
23
|
-
from dostuff.helpers.ui.emit import emit
|
|
24
|
-
emit(
|
|
25
|
-
f"Unknown context window for '{model}'; assuming {FALLBACK_CONTEXT_WINDOW:,} tokens. "
|
|
26
|
-
"Set loop.context_window in config.yaml to override.",
|
|
27
|
-
msg_type="system",
|
|
28
|
-
)
|
|
29
|
-
return FALLBACK_CONTEXT_WINDOW
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
"""OpenCode Zen (`opencode/<id>`) and OpenCode Go (`opencode-go/<id>`) models.
|
|
2
|
-
|
|
3
|
-
litellm has no OpenCode provider. Each gateway serves a model family on the endpoint of its
|
|
4
|
-
upstream API (https://opencode.ai/docs/zen/), so a model is sent through litellm's openai,
|
|
5
|
-
anthropic or gemini route with the gateway as api_base.
|
|
6
|
-
"""
|
|
7
|
-
import os
|
|
8
|
-
from typing import Any, Literal
|
|
9
|
-
|
|
10
|
-
OPENCODE_PROVIDERS = {
|
|
11
|
-
"opencode": "https://opencode.ai/inference",
|
|
12
|
-
"opencode-go": "https://opencode.ai/inference/go",
|
|
13
|
-
}
|
|
14
|
-
OPENCODE_API_KEY_ENV = "OPENCODE_API_KEY" # one console key works for Zen and Go
|
|
15
|
-
# Served on the Anthropic Messages endpoint besides Claude; they differ between the gateways
|
|
16
|
-
_ANTHROPIC_IDS = {
|
|
17
|
-
"opencode": {"qwen3.8-flash", "qwen3.6-plus", "qwen3.5-plus"},
|
|
18
|
-
"opencode-go": {"qwen3.8-max", "qwen3.8-flash", "qwen3.7-plus", "minimax-m3"},
|
|
19
|
-
}
|
|
20
|
-
# In /v1/models but not chat models
|
|
21
|
-
_NOT_CHAT = ("jev-", "test")
|
|
22
|
-
|
|
23
|
-
Endpoint = Literal["chat", "responses", "anthropic", "google"]
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def is_opencode(provider: str) -> bool:
|
|
27
|
-
return provider in OPENCODE_PROVIDERS
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def endpoint_for(provider: str, model_id: str) -> Endpoint:
|
|
31
|
-
"""Which upstream API the gateway serves `model_id` on."""
|
|
32
|
-
if model_id.startswith("claude-") or model_id in _ANTHROPIC_IDS[provider]:
|
|
33
|
-
return "anthropic"
|
|
34
|
-
if model_id.startswith(("gpt-", "grok-", "muse-")):
|
|
35
|
-
return "responses"
|
|
36
|
-
if model_id.startswith("gemini-"):
|
|
37
|
-
return "google"
|
|
38
|
-
return "chat"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def litellm_route(provider: str, model_id: str) -> tuple[str, str]:
|
|
42
|
-
"""(litellm model, api_base) for an OpenCode model id."""
|
|
43
|
-
base = OPENCODE_PROVIDERS[provider]
|
|
44
|
-
endpoint = endpoint_for(provider, model_id)
|
|
45
|
-
if endpoint == "anthropic":
|
|
46
|
-
return f"anthropic/{model_id}", f"{base}/anthropic" # litellm appends /v1/messages
|
|
47
|
-
if endpoint == "google":
|
|
48
|
-
return f"gemini/{model_id}", f"{base}/google/v1beta"
|
|
49
|
-
if endpoint == "responses":
|
|
50
|
-
return f"openai/responses/{model_id}", f"{base}/openai/v1"
|
|
51
|
-
return f"openai/{model_id}", f"{base}/openai/v1"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def opencode_kwargs(provider: str, model_id: str, api_key: str | None, session_id: str, version: str) -> dict[str, Any]:
|
|
55
|
-
"""litellm kwargs for an OpenCode model. Go asks clients to name themselves and send a
|
|
56
|
-
stable per-conversation id; Zen gets the same headers."""
|
|
57
|
-
model, api_base = litellm_route(provider, model_id)
|
|
58
|
-
return {
|
|
59
|
-
"model": model,
|
|
60
|
-
"api_base": api_base,
|
|
61
|
-
# Never fall back to OPENAI_API_KEY / ANTHROPIC_API_KEY: those would go to opencode.ai
|
|
62
|
-
"api_key": api_key or os.environ.get(OPENCODE_API_KEY_ENV) or "missing-opencode-key",
|
|
63
|
-
"extra_headers": {"User-Agent": f"dostuff/{version}", "x-opencode-session": session_id},
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def models_url(provider: str) -> str:
|
|
68
|
-
return f"{OPENCODE_PROVIDERS[provider]}/v1/models"
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def chat_model_ids(data: dict) -> list[str]:
|
|
72
|
-
"""Model ids from a /v1/models response, without the non-chat ones."""
|
|
73
|
-
return [
|
|
74
|
-
m["id"] for m in data.get("data") or []
|
|
75
|
-
if isinstance(m, dict) and m.get("id") and not str(m["id"]).startswith(_NOT_CHAT)
|
|
76
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|