otto-cli-agent 0.1.2__tar.gz → 0.1.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.
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/PKG-INFO +1 -1
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/README.md +1 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/usage.py +9 -1
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/llm_provider/anthropic_provider.py +10 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/llm_provider/base.py +30 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/llm_provider/gemini_provider.py +4 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/llm_provider/inception_provider.py +35 -16
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/llm_provider/openai_provider.py +10 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/pyproject.toml +1 -1
- otto_cli_agent-0.1.3/tests/test_prompt_caching.py +108 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/uv.lock +1 -1
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/.gitattributes +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/.github/workflows/publish.yml +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/.github/workflows/tests.yml +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/.gitignore +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/.python-version +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/CODE_OF_CONDUCT.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/CONTRIBUTING.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/LICENSE +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/SECURITY.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/README.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/__init__.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/README.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/__init__.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/art.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/chat.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/clipboard.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/context.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/doctor.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/errors.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/eval.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/eval_claw.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/eval_compaction.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/eval_hle.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/eval_memory.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/eval_swe.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/lessons.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/main.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/modals.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/models.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/output.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/route.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/serve.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/sessions.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/setup_screen.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/shell.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/tui.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/ui.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/cli/usage_panel.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/config/README.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/config/__init__.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/config/envfile.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/config/home.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/embed.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/README.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/__init__.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/claw_bench.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/compaction_bench.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/data/README.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/data/claw/README.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/data/claw/llm_judge-gemini.patch +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/data/claw/otto.yaml +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/failures.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/README.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/code_01.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/code_02.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/code_03.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/code_04.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/code_05.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/code_06.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/math_01.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/math_02.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/math_03.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/math_04.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/math_05.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/math_06.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/nphard_gcp_01.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/nphard_ksp_01.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/nphard_math_binpacking_01.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/nphard_math_clique_01.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/nphard_math_setcover_01.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/nphard_math_subsetsum_01.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/nphard_tsp_01.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/nphard_tsp_02.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/hle_bench.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/langfuse_sync.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/memory_bench.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/runner.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/single_agent.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/swe_bench.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/terminal_bench.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/memory/README.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/memory/__init__.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/memory/embeddings.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/memory/hashing.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/memory/lessons.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/memory/queue.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/memory/retrieval.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/memory/session.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/memory/sessions.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/memory/store.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/memory/tokens.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/memory/wiring.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/phone/README.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/phone/__init__.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/phone/assets/app_notes/com.android.settings.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/phone/assets/app_notes/in.amazon.mShop.android.shopping.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/phone/assets/guard_pages.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/phone/assets/guard_rules.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/phone/backend.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/phone/digest.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/phone/guard.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/phone/notes.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/phone/tools.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/README.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/__init__.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/_python_session_shim.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/browsing.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/budget.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/codemap.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/evidence.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/execution.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/modes.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/native.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/nodes.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/pricing.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/profile.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/progress.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/python_session.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/rag.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/research.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/run.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/screen.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/state.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/toolkit.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/tools.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/tracing.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/vision.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/walkthrough.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/pipeline/workspace.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/README.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/__init__.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/automap.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/health.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/llm_provider/README.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/llm_provider/__init__.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/llm_provider/custom.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/llm_provider/retired.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/llm_provider/temperature.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/mapping.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/outcomes.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/overrides.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/reload.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/router.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/setup.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/server/README.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/server/__init__.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/server/app.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/server/protocol.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/server/proxy.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/containers/README.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/containers/otto-desktop/Dockerfile +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/containers/otto-desktop/README.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/containers/otto-desktop/start-desktop.sh +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/debug_pipeline.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/docs/HISTORY.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/docs/README.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/docs/RESEARCH.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/docs/_config.yml +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/docs/design/README.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/docs/design/tiered-memory.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/docs/index.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/docs/media/codebase-code-map.png +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/docs/media/exercise-todo-cli.png +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/docs/media/otto-demo.gif +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/docs/media/palette-doctor-route.png +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/docs/media/run-trace-and-judge.png +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/docs/media/session-resumed-thinking.png +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/docs/media/sessions-picker.png +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/docs/media/setup-screen.png +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/docs/media/social-preview.png +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/docs/pypi.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/scripts/phone_page_fixture.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/scripts/phone_page_manifest.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/README.md +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/conftest.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_address_form_pincode.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_address_step.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_buy_now_sheet.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_cart.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_cart_pens.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_checkout_payment.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_checkout_payment_scrolled.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_checkout_upsell.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_filters.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_home.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_order_review.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_order_review_scrolled.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_product.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_product_buybox.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_product_ids.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_results_after_review.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_results_offers.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_results_search.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_results_sorted.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_search_suggestions.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_sign_in.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_sort_price.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_sort_sheet.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/amazon_upi_pin_screen.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/bank_pin_numpw.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/blinkit_cart_pay.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/blinkit_search.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/chat_with_otp.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/phonepe_upi_pin.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/settings_display.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/shop_onboarding_continue.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/shop_order_summary_continue.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/shop_otp_boxes.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/shop_otp_caption.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/fixtures/phone_pages/shop_qty_total_pay.json +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/phone_fakes.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_agent_loop.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_art.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_ask_user_node.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_automap.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_background_distil.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_browsing.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_budget.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_call_budget.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_chat_fast_path.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_claw_bench.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_clipboard.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_codemap.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_compaction_bench.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_custom_endpoints.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_diffusion_retry.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_embed.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_embedding_backends.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_envfile.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_eval_honesty.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_eval_runner.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_evaluator_node.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_evicted_context.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_evidence.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_failures.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_health.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_hle_bench.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_home.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_inception_provider.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_inception_stream.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_langfuse_sync.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_lessons.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_lessons_cli.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_mapping.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_memory_bench.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_memory_embeddings.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_memory_hashing.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_memory_queue.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_memory_retrieval.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_memory_session.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_memory_store.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_memory_tokens.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_memory_wiring.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_model_policies.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_modes.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_native.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_no_import_shadowing.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_optional_vendor.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_output.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_overrides.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_phone_app_notes.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_phone_backend.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_phone_call_budget.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_phone_decision.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_phone_digest.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_phone_do.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_phone_element_ids.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_phone_fold.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_phone_guard.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_phone_learned_notes.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_phone_page_corpus.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_phone_prompt.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_phone_seats.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_phone_shopping_digest.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_phone_tools.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_pipeline_nodes.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_pipeline_run.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_profile.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_progress.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_prompt_tool_sync.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_provider_connection_detail.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_python_session.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_reload.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_research_router.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_research_workflow.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_retrieval_method.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_router.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_run_scoped_toolkit.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_screen.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_seat_outcomes.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_seed_lessons.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_serve.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_sessions.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_setup.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_swe_bench.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_temperature_learning.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_terminal_bench_adapter.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_tool_loop.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_tools_stubs.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_tracing.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_tui.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_tui_progress.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_tui_setup.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_usage.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_vision_tool.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_walkthrough.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_workspace_session.py +0 -0
- {otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/tests/test_workspace_tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: otto-cli-agent
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Otto: a terminal AI agent built around measured results -- one agent loop, a rubric-first evaluator, tiered memory, multi-vendor routing, and six benchmark harnesses.
|
|
5
5
|
Project-URL: Repository, https://github.com/siddharth23P/otto_agent
|
|
6
6
|
Project-URL: Issues, https://github.com/siddharth23P/otto_agent/issues
|
|
@@ -138,6 +138,7 @@ one interpreter per run), `OTTO_PYTHON_SESSION_MEMORY_MB` (that interpreter's
|
|
|
138
138
|
address-space ceiling on Linux, default 8192, 0 for none),
|
|
139
139
|
`OTTO_EMBEDDING_MODEL` (`provider:model`; local BGE is the floor),
|
|
140
140
|
`OTTO_MODEL_PRICES` (a JSON file that overrides the price table),
|
|
141
|
+
`OTTO_PROMPT_CACHE=0` (turn off input-token caching, on by default for all four vendors),
|
|
141
142
|
`OTTO_IGNORE_ROUTES=1` (use the shipped routing table untouched; evals do),
|
|
142
143
|
`OTTO_BROWSER_PYTHON` (an interpreter with Playwright and `pyte`, which
|
|
143
144
|
enables the browser and terminal tools), `OTTO_HOME` (where state lives,
|
|
@@ -139,7 +139,13 @@ class UsageLedger:
|
|
|
139
139
|
details = usage.get("input_token_details")
|
|
140
140
|
if isinstance(details, Mapping):
|
|
141
141
|
entry.cached_input_tokens += _int(details.get("cache_read"))
|
|
142
|
-
|
|
142
|
+
# langchain-anthropic reports a write by its TTL (ephemeral_5m/_1h_input_tokens) and then
|
|
143
|
+
# leaves cache_creation at 0; either shape is a write. Priced at the 5-minute write rate,
|
|
144
|
+
# the TTL otto asks for.
|
|
145
|
+
entry.cache_write_tokens += max(
|
|
146
|
+
_int(details.get("cache_creation")),
|
|
147
|
+
_int(details.get("ephemeral_5m_input_tokens")) + _int(details.get("ephemeral_1h_input_tokens")),
|
|
148
|
+
)
|
|
143
149
|
entry.reported = entry.reported or got
|
|
144
150
|
|
|
145
151
|
@property
|
|
@@ -192,6 +198,7 @@ class UsageLedger:
|
|
|
192
198
|
"input_tokens": self.input_tokens,
|
|
193
199
|
"output_tokens": self.output_tokens,
|
|
194
200
|
"cached_input_tokens": self.cached_input_tokens,
|
|
201
|
+
"cache_write_tokens": sum(m.cache_write_tokens for m in self.by_model.values()),
|
|
195
202
|
"total_tokens": self.total_tokens,
|
|
196
203
|
"cost": self.cost,
|
|
197
204
|
"fully_priced": self.fully_priced,
|
|
@@ -202,6 +209,7 @@ class UsageLedger:
|
|
|
202
209
|
"input_tokens": m.input_tokens,
|
|
203
210
|
"output_tokens": m.output_tokens,
|
|
204
211
|
"cached_input_tokens": m.cached_input_tokens,
|
|
212
|
+
"cache_write_tokens": m.cache_write_tokens,
|
|
205
213
|
"total_tokens": m.total_tokens,
|
|
206
214
|
"reported": m.reported,
|
|
207
215
|
"cost": m.cost,
|
{otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/llm_provider/anthropic_provider.py
RENAMED
|
@@ -33,8 +33,14 @@ from agent.router.llm_provider.base import (
|
|
|
33
33
|
ProviderError,
|
|
34
34
|
ProviderUnavailable,
|
|
35
35
|
connection_detail,
|
|
36
|
+
prompt_caching,
|
|
37
|
+
with_model_kwargs,
|
|
36
38
|
)
|
|
37
39
|
|
|
40
|
+
#: The request-level breakpoint for automatic prompt caching (5-minute TTL: an agent loop
|
|
41
|
+
#: calls again well inside it, and a 1-hour write costs 2x input instead of 1.25x).
|
|
42
|
+
PROMPT_CACHE = {"type": "ephemeral"}
|
|
43
|
+
|
|
38
44
|
__all__ = ["AnthropicProvider"]
|
|
39
45
|
|
|
40
46
|
|
|
@@ -126,4 +132,8 @@ class AnthropicProvider(BaseProvider):
|
|
|
126
132
|
def chat_model(self, model_id: str, **kwargs: Any) -> BaseChatModel:
|
|
127
133
|
if self._base_url:
|
|
128
134
|
kwargs.setdefault("base_url", self._base_url)
|
|
135
|
+
if prompt_caching():
|
|
136
|
+
# Automatic caching (base.py PROMPT_CACHE_ENV): ChatAnthropic puts model_kwargs at the
|
|
137
|
+
# top level of the request, where the direct API reads `cache_control`.
|
|
138
|
+
kwargs = with_model_kwargs(kwargs, cache_control=dict(PROMPT_CACHE))
|
|
129
139
|
return ChatAnthropic(model=model_id, api_key=self._api_key, **kwargs)
|
|
@@ -184,6 +184,36 @@ class AuthError(ProviderError):
|
|
|
184
184
|
"""Key missing, malformed, or rejected by the vendor."""
|
|
185
185
|
|
|
186
186
|
|
|
187
|
+
#: Prompt (input-token) caching, on unless OTTO_PROMPT_CACHE is 0/false/off.
|
|
188
|
+
#: What "on" means differs by vendor, because their caches do:
|
|
189
|
+
#: anthropic opt-in: top-level `cache_control` (automatic caching) -- the
|
|
190
|
+
#: breakpoint follows the last block, so an agent loop re-reads its
|
|
191
|
+
#: whole transcript at 0.1x input and writes only the new part.
|
|
192
|
+
#: openai automatic for prefixes over 1024 tokens; `prompt_cache_key`
|
|
193
|
+
#: routes a model's calls to the same cache.
|
|
194
|
+
#: gemini implicit for 2.5 and later -- nothing to send; the discount
|
|
195
|
+
#: arrives as `cached_content_token_count`, already counted.
|
|
196
|
+
#: inception automatic on Mercury; the provider now reports its
|
|
197
|
+
#: `cached_input_tokens` so the ledger prices them.
|
|
198
|
+
PROMPT_CACHE_ENV = "OTTO_PROMPT_CACHE"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def prompt_caching() -> bool:
|
|
202
|
+
return os.environ.get(PROMPT_CACHE_ENV, "1").strip().lower() not in ("0", "false", "off", "no")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def with_model_kwargs(kwargs: dict[str, Any], **defaults: Any) -> dict[str, Any]:
|
|
206
|
+
"""`kwargs` with `defaults` merged under its `model_kwargs`; a route that set one of them wins,
|
|
207
|
+
and a route that set it to None removes it."""
|
|
208
|
+
extra = dict(kwargs.pop("model_kwargs", None) or {})
|
|
209
|
+
for key, value in defaults.items():
|
|
210
|
+
extra.setdefault(key, value)
|
|
211
|
+
extra = {k: v for k, v in extra.items() if v is not None}
|
|
212
|
+
if extra:
|
|
213
|
+
kwargs["model_kwargs"] = extra
|
|
214
|
+
return kwargs
|
|
215
|
+
|
|
216
|
+
|
|
187
217
|
def connection_detail(exc: BaseException) -> str:
|
|
188
218
|
"""A vendor SDK's message for a failed connection, with what failed underneath it.
|
|
189
219
|
|
|
@@ -117,6 +117,10 @@ class GeminiProvider(BaseProvider):
|
|
|
117
117
|
return found
|
|
118
118
|
|
|
119
119
|
def chat_model(self, model_id: str, **kwargs: Any) -> BaseChatModel:
|
|
120
|
+
# Prompt caching is implicit on Gemini 2.5 and later: a repeated prefix is billed at the
|
|
121
|
+
# cached rate with nothing sent, and ChatGoogleGenerativeAI reports it as
|
|
122
|
+
# input_token_details.cache_read (base.py PROMPT_CACHE_ENV). Explicit `cached_content`
|
|
123
|
+
# caches are not used -- they are stored, and billed, per hour.
|
|
120
124
|
return ChatGoogleGenerativeAI(
|
|
121
125
|
model=model_id, google_api_key=self._api_key, **kwargs
|
|
122
126
|
)
|
{otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/router/llm_provider/inception_provider.py
RENAMED
|
@@ -98,6 +98,28 @@ def _field(obj: Any, key: str) -> Any:
|
|
|
98
98
|
return getattr(obj, key, None)
|
|
99
99
|
|
|
100
100
|
|
|
101
|
+
def _cached_tokens(u: Any) -> int | None:
|
|
102
|
+
"""Mercury's cache hits: `prompt_tokens_details.cached_tokens` (what the API sends, as OpenAI
|
|
103
|
+
does) or the SDK's own `cached_input_tokens`."""
|
|
104
|
+
direct = _field(u, "cached_input_tokens")
|
|
105
|
+
if direct:
|
|
106
|
+
return direct
|
|
107
|
+
details = _field(u, "prompt_tokens_details")
|
|
108
|
+
if details is None and isinstance(getattr(u, "model_extra", None), Mapping):
|
|
109
|
+
details = u.model_extra.get("prompt_tokens_details")
|
|
110
|
+
return _field(details, "cached_tokens")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _usage_metadata(input_tokens: int, output_tokens: int, total_tokens: int, cached: Any) -> dict:
|
|
114
|
+
"""LangChain's usage shape. Mercury caches repeated prefixes by itself and reports the hits as
|
|
115
|
+
`cached_input_tokens`; they go to input_token_details.cache_read, where the ledger prices them
|
|
116
|
+
(agent/pipeline/usage.py)."""
|
|
117
|
+
metadata: dict = {"input_tokens": input_tokens, "output_tokens": output_tokens, "total_tokens": total_tokens}
|
|
118
|
+
if isinstance(cached, int) and cached > 0:
|
|
119
|
+
metadata["input_token_details"] = {"cache_read": cached}
|
|
120
|
+
return metadata
|
|
121
|
+
|
|
122
|
+
|
|
101
123
|
def _usage(u: Any) -> dict[str, int] | None:
|
|
102
124
|
"""Vendor usage payload -> Langfuse's usage_details key names."""
|
|
103
125
|
if u is None:
|
|
@@ -110,11 +132,10 @@ def _usage(u: Any) -> dict[str, int] | None:
|
|
|
110
132
|
details = {k: v for k, v in details.items() if v is not None}
|
|
111
133
|
if not details:
|
|
112
134
|
return None
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
details[key] = value
|
|
135
|
+
if cached := _cached_tokens(u):
|
|
136
|
+
details["cached_input"] = cached
|
|
137
|
+
if reasoning := _field(u, "reasoning_tokens"):
|
|
138
|
+
details["reasoning"] = reasoning
|
|
118
139
|
return details
|
|
119
140
|
|
|
120
141
|
|
|
@@ -380,11 +401,10 @@ class ChatInception(BaseChatModel):
|
|
|
380
401
|
"finish_reason": choice.finish_reason,
|
|
381
402
|
"warning": completion.warning,
|
|
382
403
|
},
|
|
383
|
-
usage_metadata=
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
},
|
|
404
|
+
usage_metadata=_usage_metadata(
|
|
405
|
+
usage.prompt_tokens, usage.completion_tokens, usage.total_tokens,
|
|
406
|
+
_cached_tokens(usage),
|
|
407
|
+
),
|
|
388
408
|
)
|
|
389
409
|
return ChatResult(
|
|
390
410
|
generations=[
|
|
@@ -449,12 +469,11 @@ class ChatInception(BaseChatModel):
|
|
|
449
469
|
yield ChatGenerationChunk(
|
|
450
470
|
message=AIMessageChunk(
|
|
451
471
|
content="",
|
|
452
|
-
usage_metadata=
|
|
453
|
-
|
|
454
|
-
"
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
},
|
|
472
|
+
usage_metadata=_usage_metadata(
|
|
473
|
+
input_tokens, output_tokens,
|
|
474
|
+
_field(usage, "total_tokens") or input_tokens + output_tokens,
|
|
475
|
+
_cached_tokens(usage),
|
|
476
|
+
),
|
|
458
477
|
)
|
|
459
478
|
)
|
|
460
479
|
continue
|
|
@@ -31,8 +31,13 @@ from agent.router.llm_provider.base import (
|
|
|
31
31
|
ProviderError,
|
|
32
32
|
ProviderUnavailable,
|
|
33
33
|
connection_detail,
|
|
34
|
+
prompt_caching,
|
|
35
|
+
with_model_kwargs,
|
|
34
36
|
)
|
|
35
37
|
|
|
38
|
+
#: prompt_cache_key is this plus the model id: one cache per model, shared by its calls.
|
|
39
|
+
PROMPT_CACHE_KEY_PREFIX = "otto:"
|
|
40
|
+
|
|
36
41
|
__all__ = ["OpenAIProvider"]
|
|
37
42
|
|
|
38
43
|
|
|
@@ -132,6 +137,11 @@ class OpenAIProvider(BaseProvider):
|
|
|
132
137
|
# No hand-rolled chat method. Returning a BaseChatModel is what makes
|
|
133
138
|
# this provider interchangeable inside a LangGraph node and visible to
|
|
134
139
|
# Langfuse without any extra wiring.
|
|
140
|
+
if prompt_caching() and self._base_url is None:
|
|
141
|
+
# OpenAI caches long prefixes by itself; the key sends a model's calls to the same cache
|
|
142
|
+
# (base.py PROMPT_CACHE_ENV). Only for OpenAI itself: an OpenAI-compatible server
|
|
143
|
+
# (custom.py) may reject a field it does not know.
|
|
144
|
+
kwargs = with_model_kwargs(kwargs, prompt_cache_key=f"{PROMPT_CACHE_KEY_PREFIX}{model_id}")
|
|
135
145
|
return ChatOpenAI(
|
|
136
146
|
model=model_id,
|
|
137
147
|
api_key=self._api_key,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "otto-cli-agent"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.3"
|
|
4
4
|
description = "Otto: a terminal AI agent built around measured results -- one agent loop, a rubric-first evaluator, tiered memory, multi-vendor routing, and six benchmark harnesses."
|
|
5
5
|
readme = "docs/pypi.md"
|
|
6
6
|
license = { file = "LICENSE" }
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Input-token caching is on for all four providers (agent/router/llm_provider/base.py
|
|
2
|
+
PROMPT_CACHE_ENV), and what each vendor reports as cached is priced as cached."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from agent.pipeline.usage import UsageLedger
|
|
8
|
+
from agent.router.llm_provider import base
|
|
9
|
+
from agent.router.llm_provider.anthropic_provider import PROMPT_CACHE, AnthropicProvider
|
|
10
|
+
from agent.router.llm_provider.inception_provider import _usage_metadata
|
|
11
|
+
from agent.router.llm_provider.openai_provider import OpenAIProvider
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _anthropic(**kw):
|
|
15
|
+
provider = AnthropicProvider.__new__(AnthropicProvider)
|
|
16
|
+
provider._api_key, provider._base_url = "sk-ant-test", kw.get("base_url")
|
|
17
|
+
return provider
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _openai(base_url=None):
|
|
21
|
+
provider = OpenAIProvider.__new__(OpenAIProvider)
|
|
22
|
+
provider._api_key, provider._base_url = "sk-test", base_url
|
|
23
|
+
return provider
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_anthropic_asks_for_automatic_caching_at_the_top_of_the_request(monkeypatch):
|
|
27
|
+
monkeypatch.delenv(base.PROMPT_CACHE_ENV, raising=False)
|
|
28
|
+
llm = _anthropic().chat_model("claude-haiku-4-5-20251001", max_tokens=64)
|
|
29
|
+
assert llm.model_kwargs["cache_control"] == PROMPT_CACHE
|
|
30
|
+
payload = llm._get_request_payload([("human", "hi")])
|
|
31
|
+
assert payload["cache_control"] == {"type": "ephemeral"}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_a_route_keeps_its_own_model_kwargs_and_may_opt_out(monkeypatch):
|
|
35
|
+
monkeypatch.delenv(base.PROMPT_CACHE_ENV, raising=False)
|
|
36
|
+
llm = _anthropic().chat_model("claude-haiku-4-5", model_kwargs={"service_tier": "auto"})
|
|
37
|
+
assert llm.model_kwargs == {"service_tier": "auto", "cache_control": PROMPT_CACHE}
|
|
38
|
+
llm = _anthropic().chat_model("claude-haiku-4-5", model_kwargs={"cache_control": {"type": "ephemeral", "ttl": "1h"}})
|
|
39
|
+
assert llm.model_kwargs["cache_control"]["ttl"] == "1h"
|
|
40
|
+
llm = _anthropic().chat_model("claude-haiku-4-5", model_kwargs={"cache_control": None})
|
|
41
|
+
assert "cache_control" not in llm.model_kwargs
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_openai_routes_a_models_calls_to_one_cache(monkeypatch):
|
|
45
|
+
monkeypatch.delenv(base.PROMPT_CACHE_ENV, raising=False)
|
|
46
|
+
llm = _openai().chat_model("gpt-5-mini")
|
|
47
|
+
assert llm.model_kwargs["prompt_cache_key"] == "otto:gpt-5-mini"
|
|
48
|
+
assert "prompt_cache_key" in llm._get_request_payload([("human", "hi")])
|
|
49
|
+
# An OpenAI-compatible server is not sent a field it may not know.
|
|
50
|
+
assert "prompt_cache_key" not in _openai("http://localhost:11434/v1").chat_model("llama3").model_kwargs
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.mark.parametrize("value", ["0", "false", "OFF"])
|
|
54
|
+
def test_the_switch_turns_it_off(monkeypatch, value):
|
|
55
|
+
monkeypatch.setenv(base.PROMPT_CACHE_ENV, value)
|
|
56
|
+
assert "cache_control" not in _anthropic().chat_model("claude-haiku-4-5").model_kwargs
|
|
57
|
+
assert "prompt_cache_key" not in _openai().chat_model("gpt-5-mini").model_kwargs
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_inception_reports_its_cached_tokens():
|
|
61
|
+
assert _usage_metadata(100, 5, 105, 80) == {
|
|
62
|
+
"input_tokens": 100, "output_tokens": 5, "total_tokens": 105,
|
|
63
|
+
"input_token_details": {"cache_read": 80}}
|
|
64
|
+
assert "input_token_details" not in _usage_metadata(100, 5, 105, 0)
|
|
65
|
+
assert "input_token_details" not in _usage_metadata(100, 5, 105, None)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_inception_streaming_carries_the_cached_count():
|
|
69
|
+
from tests.test_inception_stream import accumulate, content_chunk, model, usage_chunk
|
|
70
|
+
|
|
71
|
+
llm = model([content_chunk("hi"), usage_chunk(
|
|
72
|
+
{"prompt_tokens": 2000, "completion_tokens": 3, "total_tokens": 2003, "cached_input_tokens": 1800})])
|
|
73
|
+
assert accumulate(llm).usage_metadata["input_token_details"] == {"cache_read": 1800}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_cached_reads_and_writes_reach_the_snapshot():
|
|
77
|
+
ledger = UsageLedger()
|
|
78
|
+
ledger.record("claude-haiku-4-5", {
|
|
79
|
+
"input_tokens": 5000, "output_tokens": 10, "total_tokens": 5010,
|
|
80
|
+
"input_token_details": {"cache_read": 4000, "cache_creation": 900}})
|
|
81
|
+
snap = ledger.snapshot()
|
|
82
|
+
assert snap["cached_input_tokens"] == 4000 and snap["cache_write_tokens"] == 900
|
|
83
|
+
assert snap["models"][0]["cache_write_tokens"] == 900
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_inception_reads_the_cached_count_where_the_api_puts_it():
|
|
87
|
+
from agent.router.llm_provider.inception_provider import _cached_tokens
|
|
88
|
+
|
|
89
|
+
assert _cached_tokens({"prompt_tokens": 9, "prompt_tokens_details": {"cached_tokens": 7}}) == 7
|
|
90
|
+
assert _cached_tokens({"cached_input_tokens": 5, "prompt_tokens_details": {"cached_tokens": 0}}) == 5
|
|
91
|
+
assert not _cached_tokens({"cached_input_tokens": None, "prompt_tokens_details": {"cached_tokens": 0}})
|
|
92
|
+
assert not _cached_tokens({"prompt_tokens": 9})
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_an_anthropic_write_reported_by_its_ttl_is_priced_as_a_write():
|
|
96
|
+
# The shape langchain-anthropic 1.7 returns for a first cached call (seen live, 2026-09-17).
|
|
97
|
+
ledger = UsageLedger()
|
|
98
|
+
ledger.record("claude-haiku-4-5", {
|
|
99
|
+
"input_tokens": 12203, "output_tokens": 4, "total_tokens": 12207,
|
|
100
|
+
"input_token_details": {"cache_read": 0, "cache_creation": 0,
|
|
101
|
+
"ephemeral_5m_input_tokens": 12200, "ephemeral_1h_input_tokens": 0}})
|
|
102
|
+
assert ledger.snapshot()["cache_write_tokens"] == 12200
|
|
103
|
+
ledger.record("claude-haiku-4-5", {
|
|
104
|
+
"input_tokens": 12203, "output_tokens": 4, "total_tokens": 12207,
|
|
105
|
+
"input_token_details": {"cache_read": 12200, "cache_creation": 0,
|
|
106
|
+
"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}})
|
|
107
|
+
snap = ledger.snapshot()
|
|
108
|
+
assert snap["cached_input_tokens"] == 12200 and snap["cache_write_tokens"] == 12200
|
|
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
|
{otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/nphard_math_binpacking_01.json
RENAMED
|
File without changes
|
|
File without changes
|
{otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/nphard_math_setcover_01.json
RENAMED
|
File without changes
|
{otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/eval/golden/nphard_math_subsetsum_01.json
RENAMED
|
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
|
{otto_cli_agent-0.1.2 → otto_cli_agent-0.1.3}/agent/phone/assets/app_notes/com.android.settings.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|