Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/tmr-setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ runs:

- name: Install Claude Code CLI
shell: bash
run: npm install -g @anthropic-ai/claude-code@2.1.160
run: npm install -g @anthropic-ai/claude-code@2.1.207

- name: Configure git identity
shell: bash
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ jobs:
run: uv sync --all-packages

- name: Install Claude Code CLI
run: npm install -g @anthropic-ai/claude-code@2.1.160
run: npm install -g @anthropic-ai/claude-code@2.1.207

- name: Run Docker release tests
run: PYTEST_MAX_DURATION_SECONDS=900 uv run pytest -n 0 --timeout 600 --no-cov --cov-fail-under=0 -v --tb=short --ignore apps/minds -m '(docker or docker_sdk) and release'
Expand Down Expand Up @@ -117,7 +117,7 @@ jobs:
run: brew install tmux unison

- name: Install Claude Code CLI
run: npm install -g @anthropic-ai/claude-code@2.1.160
run: npm install -g @anthropic-ai/claude-code@2.1.207

- name: Configure Docker Hub mirror
if: runner.os == 'Linux'
Expand Down
4 changes: 4 additions & 0 deletions apps/modal_litellm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ even on litellm versions whose bundled price map predates a model. The model
list lives in `apps/modal_litellm/app.py` (`LITELLM_CONFIG`) and is mirrored in
`litellm_proxy/config.yaml`; `config_drift_test.py` fails if the two diverge.

Fable ($10 / $50 per 1M input / output):

- `claude-fable-5`

Opus (current price tier, $5 / $25 per 1M input / output):

- `claude-opus-4-8` (latest Opus)
Expand Down
8 changes: 8 additions & 0 deletions apps/modal_litellm/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@
# predates a model (e.g. claude-opus-4-8 only landed in litellm's price map
# in the 1.88.0 pre-release line). MUST stay in sync with
# litellm_proxy/config.yaml -- config_drift_test.py enforces this.
_FABLE_PRICING = {
"input_cost_per_token": 0.00001,
"output_cost_per_token": 0.00005,
"cache_creation_input_token_cost": 0.0000125,
"cache_read_input_token_cost": 0.000001,
}
_OPUS_PRICING = {
"input_cost_per_token": 0.000005,
"output_cost_per_token": 0.000025,
Expand Down Expand Up @@ -108,6 +114,8 @@ def _model_entry(model_name: str, pricing: dict[str, float]) -> dict[str, object

LITELLM_CONFIG = {
"model_list": [
# Fable line.
_model_entry("claude-fable-5", _FABLE_PRICING),
# Current Opus line.
_model_entry("claude-opus-4-8", _OPUS_PRICING),
_model_entry("claude-opus-4-7", _OPUS_PRICING),
Expand Down
1 change: 1 addition & 0 deletions apps/modal_litellm/changelog/mngr-fable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `claude-fable-5` to the LiteLLM proxy model list with inline pricing ($10 / $50 per 1M input / output tokens, cache write 1.25e-5, cache read 1e-6, mirrored from litellm's price map), so workspaces using imbue_cloud virtual keys can run Claude Fable 5 with correct cost tracking.
3 changes: 3 additions & 0 deletions dev/changelog/mngr-fable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Bump the pinned Claude Code CLI version from 2.1.160 to 2.1.207 in CI workflows (`release-tests.yml`, `tmr-setup` action) and the minds e2e snapshot script, matching the new workspace pin that supports Claude Fable 5.

- Add `claude-fable-5` with inline pricing ($10 / $50 per 1M input / output tokens, cache write 1.25e-5, cache read 1e-6) to the repo-root local-dev LiteLLM proxy config (`litellm_proxy/config.yaml`), kept in sync with `apps/modal_litellm/app.py` by a drift test.
1 change: 1 addition & 0 deletions libs/mngr/changelog/mngr-fable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Bump the release-sandbox Dockerfile's pinned Claude Code version from 2.1.160 to 2.1.207, which supports the Claude Fable 5 model. Must land together with the matching `[agent_types.claude].version` bump in default-workspace-template.
2 changes: 1 addition & 1 deletion libs/mngr/imbue/mngr/resources/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ ENV PATH="/root/.local/bin:$PATH"
# .mngr/settings.toml pin (`[agent_types.claude].version`). The release-test
# `test_claude_code_version_matches_default_workspace_template_pin` enforces the
# sync; bump both together when rolling a new claude release.
ARG CLAUDE_CODE_VERSION="2.1.160"
ARG CLAUDE_CODE_VERSION="2.1.207"
RUN curl -fsSL https://claude.ai/install.sh > /tmp/install_claude.sh && ( if [ -n "$CLAUDE_CODE_VERSION" ]; then cat /tmp/install_claude.sh | bash -s "$CLAUDE_CODE_VERSION"; else cat /tmp/install_claude.sh | bash; fi && test -x /root/.local/bin/claude ) || ( cat /tmp/install_claude.sh && exit 1 )
ENV CLAUDE_CODE_VERSION=${CLAUDE_CODE_VERSION}

Expand Down
1 change: 1 addition & 0 deletions libs/mngr_claude/changelog/mngr-fable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `auto_disable_questions` to `ClaudeAgentConfig`: when True, appends `--disallowed-tools AskUserQuestion` to the agent invocation so unattended agents can't block on questions (recent Claude Code releases no longer treat AskUserQuestion as a permission prompt, so `auto_allow_permissions` alone no longer covers it). Appended as a separate flag; Claude accumulates repeated `--disallowed-tools` flags, so a list already supplied via `cli_args` is preserved.
8 changes: 8 additions & 0 deletions libs/mngr_claude/imbue/mngr_claude/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,11 @@ class ClaudeAgentConfig(AgentTypeConfig):
description="When True, adds a PermissionRequest hook that auto-allows all permission dialogs. "
"This means Claude Code will never pause for permission approval.",
)
auto_disable_questions: bool = Field(
default=False,
description="When True, adds `--disallowed-tools AskUserQuestion` to the agent invocation to "
"prevent it from ever asking questions (which can cause the agent to get blocked)",
)
settings_overrides: Annotated[dict[str, Any], SettingsPatchField()] = Field(
default_factory=dict,
description="A patch merged onto your home Claude settings at provisioning. A bare key assigns "
Expand Down Expand Up @@ -2360,6 +2365,9 @@ def assemble_command(
# (Claude is last-wins) -- the accepted, documented limitation of that mode.
cli_args = self.agent_config.cli_args
all_extra_args = cli_args + quote_agent_args(agent_args)
# Claude appends & unions repeated --disallowed-tools flags.
if self.agent_config.auto_disable_questions:
all_extra_args = all_extra_args + ("--disallowed-tools", "AskUserQuestion")
args_str = " ".join(all_extra_args) if all_extra_args else ""

# Read the latest session ID from the tracking file written by the SessionStart hook.
Expand Down
36 changes: 36 additions & 0 deletions libs/mngr_claude/imbue/mngr_claude/plugin_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,42 @@ def test_claude_agent_assemble_command_with_cli_args_and_agent_args(
assert parsed.create_args == ["--verbose", "--model", "opus"]


def test_claude_agent_assemble_command_auto_disable_questions_appends_flag(
local_provider: LocalProviderInstance, tmp_path: Path, temp_mngr_ctx: MngrContext
) -> None:
"""auto_disable_questions=True should append --disallowed-tools AskUserQuestion after all other args."""
agent, host = make_claude_agent(
local_provider,
tmp_path,
temp_mngr_ctx,
agent_config=ClaudeAgentConfig(
cli_args=("--disallowed-tools", "TodoWrite"),
auto_disable_questions=True,
check_installation=False,
),
)

command = agent.assemble_command(host=host, agent_args=(), command_override=None)

parsed = _ParsedAssembleCommand(str(command))
# Appended as a second --disallowed-tools flag (claude appends & unions
# repeated flags), so the cli_args-supplied list is preserved.
expected = ["--disallowed-tools", "TodoWrite", "--disallowed-tools", "AskUserQuestion"]
assert parsed.resume_args == expected
assert parsed.create_args == expected


def test_claude_agent_assemble_command_auto_disable_questions_off_by_default(
local_provider: LocalProviderInstance, tmp_path: Path, temp_mngr_ctx: MngrContext
) -> None:
"""Default config should not add any --disallowed-tools flag."""
agent, host = make_claude_agent(local_provider, tmp_path, temp_mngr_ctx)

command = agent.assemble_command(host=host, agent_args=(), command_override=None)

assert "--disallowed-tools" not in shlex.split(str(command))


def test_claude_agent_assemble_command_passes_user_settings_through(
local_provider: LocalProviderInstance, tmp_path: Path, temp_mngr_ctx: MngrContext
) -> None:
Expand Down
1 change: 1 addition & 0 deletions libs/mngr_robinhood/changelog/mngr-fable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a Claude Fable family entry ($10 / $50 per 1M input / output tokens, cache write $12.50, cache read $1.00) to the Agent SDK per-model price table, so `ResultMessage.total_cost_usd` is populated for `claude-fable-5` sessions instead of being left unset.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ class ModelPricing(FrozenModel):
cache_write_usd_per_million=Decimal("6.25"),
cache_read_usd_per_million=Decimal("0.50"),
)
_FABLE_PRICING: Final[ModelPricing] = ModelPricing(
input_usd_per_million=Decimal("10.00"),
output_usd_per_million=Decimal("50.00"),
cache_write_usd_per_million=Decimal("12.50"),
cache_read_usd_per_million=Decimal("1.00"),
)

# Model-id substrings mapped to their pricing, checked in order. claude model ids look like
# ``claude-haiku-4-5-20251001`` / ``claude-sonnet-4-6`` / ``claude-opus-4-8``, so a family
Expand All @@ -60,6 +66,7 @@ class ModelPricing(FrozenModel):
("haiku", _HAIKU_PRICING),
("sonnet", _SONNET_PRICING),
("opus", _OPUS_PRICING),
("fable", _FABLE_PRICING),
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ def test_resolve_model_pricing_matches_family_for_dated_and_alias_ids() -> None:
assert resolve_model_pricing("claude-haiku-4-5-20251001") is resolve_model_pricing("haiku")
assert resolve_model_pricing("claude-sonnet-4-6") is resolve_model_pricing("sonnet")
assert resolve_model_pricing("claude-opus-4-8") is resolve_model_pricing("opus")
assert resolve_model_pricing("claude-fable-5") is resolve_model_pricing("fable")


def test_resolve_model_pricing_is_none_for_unknown_model() -> None:
Expand Down
1 change: 1 addition & 0 deletions libs/mngr_usage/changelog/mngr-fable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `anthropic/claude-fable-5` to the usage pricing table ($10 / $50 per 1M input / output tokens), so token-only usage sources report cost for Claude Fable 5 instead of showing it as unpriced.
7 changes: 7 additions & 0 deletions libs/mngr_usage/imbue/mngr_usage/pricing.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ class PerTokenPrices(FrozenModel):
# Anthropic per-token pricing, mirrored verbatim from apps/modal_litellm/app.py
# (which itself mirrors litellm's map). Grouped by tier so the "same price"
# relationship across model ids stays explicit, exactly as modal_litellm does.
_FABLE_PRICES: Final[PerTokenPrices] = PerTokenPrices(
input_cost_per_token=0.00001,
output_cost_per_token=0.00005,
cache_creation_input_token_cost=0.0000125,
cache_read_input_token_cost=0.000001,
)
_OPUS_PRICES: Final[PerTokenPrices] = PerTokenPrices(
input_cost_per_token=0.000005,
output_cost_per_token=0.000025,
Expand Down Expand Up @@ -122,6 +128,7 @@ class PerTokenPrices(FrozenModel):
# apps/modal_litellm (drift test); OpenAI stays in sync with litellm directly
# (litellm_pricing_test).
MODEL_PRICING: Final[dict[str, PerTokenPrices]] = {
"anthropic/claude-fable-5": _FABLE_PRICES,
"anthropic/claude-opus-4-8": _OPUS_PRICES,
"anthropic/claude-opus-4-7": _OPUS_PRICES,
"anthropic/claude-opus-4-6": _OPUS_PRICES,
Expand Down
10 changes: 10 additions & 0 deletions litellm_proxy/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@
# model_prices_and_context_window map) so cost tracking is correct even on
# litellm versions whose bundled price map predates a model.
model_list:
# Fable line.
- model_name: claude-fable-5
litellm_params:
model: anthropic/claude-fable-5
api_key: os.environ/ANTHROPIC_API_KEY
input_cost_per_token: 0.00001
output_cost_per_token: 0.00005
cache_creation_input_token_cost: 0.0000125
cache_read_input_token_cost: 0.000001

# Current Opus line.
- model_name: claude-opus-4-8
litellm_params:
Expand Down
2 changes: 1 addition & 1 deletion scripts/snapshot_minds_e2e_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
# Keep these in sync with apps/minds/.nvmrc and apps/minds/package.json engines.
_NODE_VERSION: Final[str] = "24.15.0"
_PNPM_VERSION: Final[str] = "10.33.4"
_CLAUDE_CODE_VERSION: Final[str] = "2.1.160"
_CLAUDE_CODE_VERSION: Final[str] = "2.1.207"

# In-sandbox entrypoint that invokes the shared e2e workspace runner the
# pytest test also uses, but without the test's mngr-destroy cleanup. The
Expand Down