tokenjam 0.3.2__tar.gz → 0.3.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.
- {tokenjam-0.3.2 → tokenjam-0.3.3}/CLAUDE.md +2 -2
- {tokenjam-0.3.2 → tokenjam-0.3.3}/CONTRIBUTING.md +2 -2
- {tokenjam-0.3.2 → tokenjam-0.3.3}/PKG-INFO +1 -1
- {tokenjam-0.3.2 → tokenjam-0.3.3}/pyproject.toml +1 -1
- {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/package.json +1 -1
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_cmd_tokenmaxx.py +55 -12
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_cost.py +15 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_tokenmaxx.py +143 -54
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/cost.py +1 -1
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/pricing/models.toml +5 -5
- tokenjam-0.3.2/pricing/models.toml +0 -82
- {tokenjam-0.3.2 → tokenjam-0.3.3}/.github/CODEOWNERS +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/.github/ISSUE_TEMPLATE/integration_request.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/.github/pull_request_template.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/.github/workflows/ci.yml +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/.github/workflows/publish-npm.yml +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/.github/workflows/publish-pypi.yml +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/.gitignore +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/AGENTS.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/CHANGELOG.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/LICENSE +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/Makefile +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/README.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/SECURITY.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/alerts.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/architecture.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/backfill/helicone.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/backfill/langfuse.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/backfill/otlp.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/backfill/overview.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/claude-code-integration.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/cli-reference.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/configuration.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/export.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/framework-support.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/installation.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/internal/specs/.gitkeep +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/nemoclaw-integration.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/openclaw.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/optimize/cache.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/optimize/downsize.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/optimize/script.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/optimize/trim.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/policy/overview.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/python-sdk.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/screenshots/tj-alerts.png +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/screenshots/tj-budget.png +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/screenshots/tj-cost.png +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/screenshots/tj-status.png +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/screenshots/tj-traces.png +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/typescript-sdk.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/README.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/alerts_and_drift/_shared.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/alerts_and_drift/budget_breach_demo.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/alerts_and_drift/drift_demo.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/alerts_and_drift/sensitive_actions_demo.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/multi/rag_pipeline.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/multi/research_team.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/multi/router_agent.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/multi/sample_docs/agent_patterns.txt +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/multi/sample_docs/cost_management.txt +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/multi/sample_docs/observability.txt +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/multi/sample_docs/safety.txt +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/openclaw/README.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_framework/autogen_agent.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_framework/crewai_agent.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_framework/langchain_agent.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_framework/langgraph_agent.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_framework/llamaindex_agent.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_provider/anthropic_agent.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_provider/bedrock_agent.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_provider/gemini_agent.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_provider/litellm_agent.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_provider/openai_agent.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_provider/openai_agents_sdk_agent.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/incidents/hallucination-drift/BLOG.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/incidents/hallucination-drift/README.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/incidents/hallucination-drift/scenario.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/incidents/retry-loop/BLOG.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/incidents/retry-loop/README.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/incidents/retry-loop/scenario.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/incidents/surprise-cost/BLOG.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/incidents/surprise-cost/README.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/incidents/surprise-cost/scenario.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/README.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/package-lock.json +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/src/client.test.ts +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/src/client.ts +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/src/index.ts +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/src/semconv.test.ts +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/src/semconv.ts +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/src/span-builder.test.ts +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/src/span-builder.ts +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/src/types.ts +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/tsconfig.json +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/agents/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/agents/email_agent_budget_breach.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/agents/email_agent_drift.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/agents/email_agent_loop.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/agents/email_agent_normal.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/agents/mock_llm.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/agents/test_mock_scenarios.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/conftest.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/e2e/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/e2e/conftest.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/e2e/test_real_llm.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/factories.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/fixtures/helicone_real_response.json +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/fixtures/langfuse_real_response.json +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/fixtures/otlp_sample.json +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/integration/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/integration/test_api.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/integration/test_cli.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/integration/test_db.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/integration/test_demos.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/integration/test_full_pipeline.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/integration/test_logs_api.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/manual-new-release-tests.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/manual-pre-release-testing.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/synthetic/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/synthetic/test_alert_rules.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/synthetic/test_cost_tracking.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/synthetic/test_drift_detection.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/synthetic/test_ingest.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/synthetic/test_schema_validation.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/toy_agent/toy_agent.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_alerts.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_backfill.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_cache_efficacy.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_cache_recommend.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_cmd_policy.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_cmd_stop.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_compare.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_config.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_config_secret_divergence.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_demo_env.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_demo_scenarios.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_drift.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_export_claude_code.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_formatting.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_ingest_helicone.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_ingest_langfuse.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_ingest_otlp.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_litellm_client.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_litellm_integration.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_logs_converter.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_mcp_server.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_models.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_onboard_codex.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_onboard_daemon.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_openclaw_ingest.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_optimize.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_pricing_override.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_prompt_bloat.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_spans_stats_repair.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_time_parse.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_transport_401.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_workflow_restructure.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/app.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/deps.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/middleware.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/agents.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/alerts.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/budget.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/cost.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/cost_compare.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/drift.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/logs.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/metrics.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/optimize.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/otlp.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/spans.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/status.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/tools.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/traces.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_alerts.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_backfill.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_budget.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_cost.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_demo.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_doctor.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_drift.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_export.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_mcp.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_onboard.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_optimize.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_policy.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_report.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_serve.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_status.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_stop.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_tools.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_traces.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_uninstall.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/main.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/alerts.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/api_backend.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/backfill.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/config.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/db.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/drift.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/export/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/export/claude_code.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/ingest.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/ingest_adapters/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/ingest_adapters/helicone.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/ingest_adapters/langfuse.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/ingest_adapters/otlp.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/models.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/README.md +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/analyzers/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/analyzers/budget_projection.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/analyzers/cache_efficacy.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/analyzers/cache_recommend.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/analyzers/model_downgrade.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/analyzers/prompt_bloat.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/analyzers/workflow_restructure.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/registry.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/runner.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/types.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/pricing.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/retention.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/schema_validator.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/demo/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/demo/env.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/mcp/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/mcp/server.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/otel/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/otel/exporters.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/otel/otlp_parsing.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/otel/provider.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/otel/semconv.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/py.typed +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/agent.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/bootstrap.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/client.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/http_exporter.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/anthropic.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/autogen.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/base.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/bedrock.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/crewai.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/gemini.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/langchain.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/langgraph.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/litellm.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/llamaindex.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/nemoclaw.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/openai.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/openai_agents_sdk.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/transport.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/ui/index.html +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/utils/__init__.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/utils/formatting.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/utils/ids.py +0 -0
- {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/utils/time_parse.py +0 -0
|
@@ -191,9 +191,9 @@ The Agent Incident Library at `incidents/` is separate: each scenario is a `scen
|
|
|
191
191
|
|
|
192
192
|
## Pricing
|
|
193
193
|
|
|
194
|
-
Model pricing lives in `pricing/models.toml` (USD per million tokens). Structure: `[provider.model_name]` with `input_per_mtok`, `output_per_mtok`, and optional `cache_read_per_mtok`/`cache_write_per_mtok`. Unknown models fall back to default rates ($0.50/$2.00 per MTok) with a logged warning. The pricing table is LRU-cached at process startup — restart to pick up changes.
|
|
194
|
+
Model pricing lives in `tokenjam/pricing/models.toml` (USD per million tokens) — the packaged file `core/pricing.py` loads via `PRICING_FILE = Path(__file__).parent.parent / "pricing" / "models.toml"`. There is no repo-root `pricing/` copy (it was moved into the package in v0.1.x so it ships in the wheel; editing a repo-root file would have no runtime effect). Structure: `[provider.model_name]` with `input_per_mtok`, `output_per_mtok`, and optional `cache_read_per_mtok`/`cache_write_per_mtok`. Unknown models fall back to default rates ($0.50/$2.00 per MTok) with a logged warning. The pricing table is LRU-cached at process startup — restart to pick up changes.
|
|
195
195
|
|
|
196
|
-
Pricing is community-maintained: submit a PR editing `pricing/models.toml` when provider prices change. No code changes needed — the file is loaded at runtime.
|
|
196
|
+
Pricing is community-maintained: submit a PR editing `tokenjam/pricing/models.toml` when provider prices change. No code changes needed — the file is loaded at runtime.
|
|
197
197
|
|
|
198
198
|
## CI
|
|
199
199
|
|
|
@@ -43,7 +43,7 @@ tokenjam/sdk/ @watch() decorator and provider/framework patches
|
|
|
43
43
|
tokenjam/otel/ OTel TracerProvider and span exporter wiring
|
|
44
44
|
tokenjam/utils/ Formatting, time parsing, ID generation
|
|
45
45
|
sdk-ts/src/ TypeScript SDK (@tokenjam/sdk)
|
|
46
|
-
pricing/models.toml
|
|
46
|
+
tokenjam/pricing/models.toml Community-maintained model pricing — PRs welcome here
|
|
47
47
|
tests/factories.py Span factory — use this in all synthetic tests, never
|
|
48
48
|
construct NormalizedSpan directly
|
|
49
49
|
```
|
|
@@ -57,7 +57,7 @@ This project was built using parallel Claude Code agents. The `.claude/` directo
|
|
|
57
57
|
|
|
58
58
|
## Pricing table contributions
|
|
59
59
|
|
|
60
|
-
The file `pricing/models.toml` is intentionally community-maintained. If a model is missing or prices have changed, open a PR with the update — no issue needed, just update the TOML and verify the format matches existing entries.
|
|
60
|
+
The file `tokenjam/pricing/models.toml` is intentionally community-maintained. If a model is missing or prices have changed, open a PR with the update — no issue needed, just update the TOML and verify the format matches existing entries. (This is the file the cost engine loads at runtime; there is no separate repo-root copy.)
|
|
61
61
|
|
|
62
62
|
## Reporting issues
|
|
63
63
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tokenjam
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: TokenJam — local-first OTel-native observability for Autonomous AI agents
|
|
5
5
|
Project-URL: Homepage, https://opencla.watch
|
|
6
6
|
Project-URL: Repository, https://github.com/Metabuilder-Labs/openclawwatch
|
|
@@ -14,27 +14,54 @@ from tokenjam.core.config import ProviderBudget, TjConfig
|
|
|
14
14
|
# ───────────────────────────── classification ─────────────────────────────
|
|
15
15
|
|
|
16
16
|
def test_classify_zero_spend_is_sipper():
|
|
17
|
-
|
|
18
|
-
assert
|
|
17
|
+
# Zero spend → Sipper, regardless of which path.
|
|
18
|
+
assert _classify(0.0).label == "TokenSipper"
|
|
19
|
+
assert _classify(0.0, multiplier=0.0).label == "TokenSipper"
|
|
19
20
|
|
|
20
21
|
|
|
21
|
-
def
|
|
22
|
-
#
|
|
22
|
+
def test_classify_multiplier_path_walks_tier_boundaries():
|
|
23
|
+
# Multiplier-based classification — the primary path for subscription users.
|
|
24
|
+
# Boundaries are 1× / 4× / 10× / 20× per the launch-tier spec.
|
|
23
25
|
cases = [
|
|
24
|
-
(
|
|
25
|
-
(
|
|
26
|
-
(
|
|
27
|
-
(
|
|
28
|
-
(
|
|
29
|
-
(
|
|
30
|
-
(
|
|
31
|
-
(
|
|
26
|
+
(0.99, "TokenSipper"),
|
|
27
|
+
(1.0, "TokenModerator"),
|
|
28
|
+
(3.99, "TokenModerator"),
|
|
29
|
+
(4.0, "TokenMaxxer"),
|
|
30
|
+
(9.99, "TokenMaxxer"),
|
|
31
|
+
(10.0, "TokenChad"),
|
|
32
|
+
(19.99, "TokenChad"),
|
|
33
|
+
(20.0, "TokenGigaChad"),
|
|
34
|
+
(200.0, "TokenGigaChad"),
|
|
35
|
+
]
|
|
36
|
+
for mult, expected in cases:
|
|
37
|
+
assert _classify(0.0, multiplier=mult).label == expected, f"failed at {mult}×"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_classify_absolute_path_for_api_users_walks_tier_boundaries():
|
|
41
|
+
# Absolute USD/mo fallback — calibrated against Max-5x = $100/mo so the
|
|
42
|
+
# tier names mean roughly the same thing in both paths.
|
|
43
|
+
cases = [
|
|
44
|
+
(99.99, "TokenSipper"),
|
|
45
|
+
(100.0, "TokenModerator"),
|
|
46
|
+
(399.99, "TokenModerator"),
|
|
47
|
+
(400.0, "TokenMaxxer"),
|
|
48
|
+
(999.99, "TokenMaxxer"),
|
|
49
|
+
(1000.0, "TokenChad"),
|
|
50
|
+
(1999.99, "TokenChad"),
|
|
51
|
+
(2000.0, "TokenGigaChad"),
|
|
32
52
|
(50_000, "TokenGigaChad"),
|
|
33
53
|
]
|
|
34
54
|
for spend, expected in cases:
|
|
35
55
|
assert _classify(spend).label == expected, f"failed at ${spend}"
|
|
36
56
|
|
|
37
57
|
|
|
58
|
+
def test_classify_multiplier_overrides_absolute_when_both_provided():
|
|
59
|
+
# A subscription user at $50/mo on a $20 Pro plan (2.5× their plan) is
|
|
60
|
+
# a TokenModerator, NOT a TokenSipper — the multiplier path wins.
|
|
61
|
+
t = _classify(50.0, multiplier=2.5)
|
|
62
|
+
assert t.label == "TokenModerator"
|
|
63
|
+
|
|
64
|
+
|
|
38
65
|
def test_every_tier_has_emoji_and_quip():
|
|
39
66
|
# The artifact's social-shareability depends on every tier having
|
|
40
67
|
# readable text — guard against a future blank entry.
|
|
@@ -95,3 +122,19 @@ def test_plan_label_and_fee_returns_none_for_unknown_or_api():
|
|
|
95
122
|
assert _plan_label_and_fee("local") is None
|
|
96
123
|
assert _plan_label_and_fee(None) is None
|
|
97
124
|
assert _plan_label_and_fee("not_a_plan") is None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_all_anthropic_subscription_plans_produce_a_multiplier():
|
|
128
|
+
# Every Anthropic subscription plan in the onboard wizard must produce
|
|
129
|
+
# a finite multiplier, otherwise the tokenmaxx tweet hook ("Nx your
|
|
130
|
+
# plan") doesn't render for those users. This guards against the table
|
|
131
|
+
# diverging from `cmd_onboard.py::_ANTHROPIC_PLAN_CHOICES`.
|
|
132
|
+
for plan in ("pro", "max_5x", "max_20x"):
|
|
133
|
+
info = _plan_label_and_fee(plan)
|
|
134
|
+
assert info is not None, f"{plan!r} not in plan-fee table"
|
|
135
|
+
label, fee = info
|
|
136
|
+
assert label, f"{plan!r} missing a display label"
|
|
137
|
+
assert fee and fee > 0, f"{plan!r} missing a numeric monthly fee"
|
|
138
|
+
# Sanity-check the multiplier math the renderer does.
|
|
139
|
+
spend = 1000.0
|
|
140
|
+
assert spend / fee > 0
|
|
@@ -113,6 +113,21 @@ def test_get_rates_opus_4_8():
|
|
|
113
113
|
assert rates.cache_write_per_mtok == 6.25
|
|
114
114
|
|
|
115
115
|
|
|
116
|
+
def test_calculate_cost_opus_4_5_model():
|
|
117
|
+
# claude-opus-4-5: input=5.00, output=25.00 (same tier as 4.6/4.7/4.8)
|
|
118
|
+
cost = calculate_cost("anthropic", "claude-opus-4-5", 1_000_000, 1_000_000)
|
|
119
|
+
assert cost == 30.0
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_get_rates_opus_4_5():
|
|
123
|
+
rates = get_rates("anthropic", "claude-opus-4-5")
|
|
124
|
+
assert rates is not None
|
|
125
|
+
assert rates.input_per_mtok == 5.00
|
|
126
|
+
assert rates.output_per_mtok == 25.00
|
|
127
|
+
assert rates.cache_read_per_mtok == 0.50
|
|
128
|
+
assert rates.cache_write_per_mtok == 6.25
|
|
129
|
+
|
|
130
|
+
|
|
116
131
|
def test_calculate_cost_openai_model():
|
|
117
132
|
# gpt-4o: input=2.50, output=10.00
|
|
118
133
|
cost = calculate_cost("openai", "gpt-4o", 500_000, 100_000)
|
|
@@ -20,13 +20,17 @@ from dataclasses import dataclass
|
|
|
20
20
|
|
|
21
21
|
import click
|
|
22
22
|
|
|
23
|
-
from tokenjam.utils.formatting import console, format_cost
|
|
23
|
+
from tokenjam.utils.formatting import console, format_cost # noqa: F401 (kept for back-compat imports)
|
|
24
24
|
from tokenjam.utils.time_parse import parse_since, utcnow
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
# Tier table —
|
|
28
|
-
#
|
|
29
|
-
#
|
|
27
|
+
# Tier table — multiplier thresholds (×plan-fee) → (label, one-liner).
|
|
28
|
+
# The `threshold` field is interpreted as a multiplier when the user has a
|
|
29
|
+
# subscription plan declared, or as an absolute USD/mo amount when they
|
|
30
|
+
# don't (API users, no plan set). The fallback thresholds in _SPEND_TIERS
|
|
31
|
+
# below mirror the multiplier ladder calibrated against the Max-5x plan
|
|
32
|
+
# ($100/mo) so the tier names mean roughly the same thing across paths.
|
|
33
|
+
# Order matters: classify walks high-to-low; first match wins.
|
|
30
34
|
@dataclass(frozen=True)
|
|
31
35
|
class Tier:
|
|
32
36
|
threshold: float
|
|
@@ -36,18 +40,34 @@ class Tier:
|
|
|
36
40
|
|
|
37
41
|
|
|
38
42
|
_TIERS: list[Tier] = [
|
|
39
|
-
Tier(0,
|
|
40
|
-
Tier(
|
|
41
|
-
Tier(
|
|
42
|
-
Tier(
|
|
43
|
-
Tier(
|
|
43
|
+
Tier(0, "TokenSipper", "💧", "Are you even using AI?"),
|
|
44
|
+
Tier(1, "TokenModerator", "🥱", "Mostly reasonable. Try harder."),
|
|
45
|
+
Tier(4, "TokenMaxxer", "💸", "You're paying Anthropic's rent."),
|
|
46
|
+
Tier(10, "TokenChad", "🔥", "You're paying their interns' rent too."),
|
|
47
|
+
Tier(20, "TokenGigaChad", "🔥🔥", "Touch grass. Then run `tj optimize`."),
|
|
44
48
|
]
|
|
45
49
|
|
|
50
|
+
# Absolute USD/mo fallback for users without a subscription plan (API users).
|
|
51
|
+
# Calibrated against Max-5x = $100/mo: each threshold is the multiplier × $100.
|
|
52
|
+
# This way a $400/mo API user and a 4× Pro/Max-5x/Max-20x user both end up
|
|
53
|
+
# in TokenMaxxer — the tier name reflects "shocking spend" in either world.
|
|
54
|
+
_SPEND_TIER_THRESHOLDS_USD: list[float] = [0, 100, 400, 1000, 2000]
|
|
46
55
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
|
|
57
|
+
def _classify(monthly_spend: float, multiplier: float | None = None) -> Tier:
|
|
58
|
+
"""
|
|
59
|
+
Pick a tier. Prefer the multiplier path when the user has a subscription
|
|
60
|
+
plan with a declared fee; fall back to absolute monthly spend when not.
|
|
61
|
+
Walks tiers high-to-low; first matching threshold wins.
|
|
62
|
+
"""
|
|
63
|
+
if multiplier is not None:
|
|
64
|
+
for tier in reversed(_TIERS):
|
|
65
|
+
if multiplier >= tier.threshold:
|
|
66
|
+
return tier
|
|
67
|
+
return _TIERS[0]
|
|
68
|
+
# API / no-plan path — map onto the same tier labels via absolute USD.
|
|
69
|
+
for tier, threshold_usd in zip(reversed(_TIERS), reversed(_SPEND_TIER_THRESHOLDS_USD)):
|
|
70
|
+
if monthly_spend >= threshold_usd:
|
|
51
71
|
return tier
|
|
52
72
|
return _TIERS[0]
|
|
53
73
|
|
|
@@ -86,7 +106,7 @@ def cmd_tokenmaxx(ctx: click.Context, since: str, output_json: bool) -> None:
|
|
|
86
106
|
if plan_info and plan_info[1]:
|
|
87
107
|
multiplier = monthly_spend / plan_info[1]
|
|
88
108
|
|
|
89
|
-
tier = _classify(monthly_spend)
|
|
109
|
+
tier = _classify(monthly_spend, multiplier)
|
|
90
110
|
|
|
91
111
|
if output_json:
|
|
92
112
|
click.echo(json.dumps({
|
|
@@ -201,6 +221,28 @@ def _plan_label_and_fee(plan_tier: str | None) -> tuple[str, float | None] | Non
|
|
|
201
221
|
|
|
202
222
|
# ───────────────────────────── rendering ──────────────────────────────────
|
|
203
223
|
|
|
224
|
+
def _fmt_spend(usd: float) -> str:
|
|
225
|
+
"""Spend / savings: always 2 decimals — readable, screenshot-friendly.
|
|
226
|
+
|
|
227
|
+
The default `format_cost` helper uses 4 decimals (precision for the cost
|
|
228
|
+
engine internals); for the tokenmaxx social artifact we want $4044.57,
|
|
229
|
+
not $4044.5774.
|
|
230
|
+
"""
|
|
231
|
+
return f"${usd:.2f}"
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _fmt_fee(usd: float) -> str:
|
|
235
|
+
"""Plan fee: drop the decimals when the fee is a round dollar.
|
|
236
|
+
|
|
237
|
+
Anthropic / OpenAI subscription tiers (Pro $20, Max-5x $100, Max-20x $200)
|
|
238
|
+
are all whole-dollar — `$100.00` reads worse than `$100`. Falls back to
|
|
239
|
+
2 decimals for anything fractional.
|
|
240
|
+
"""
|
|
241
|
+
if usd == int(usd):
|
|
242
|
+
return f"${int(usd)}"
|
|
243
|
+
return f"${usd:.2f}"
|
|
244
|
+
|
|
245
|
+
|
|
204
246
|
def _render(
|
|
205
247
|
*, tier: Tier, spend_usd: float, monthly_spend: float,
|
|
206
248
|
window_days: int, sessions: int,
|
|
@@ -208,7 +250,12 @@ def _render(
|
|
|
208
250
|
multiplier: float | None,
|
|
209
251
|
savings_usd: float,
|
|
210
252
|
) -> None:
|
|
211
|
-
"""
|
|
253
|
+
"""
|
|
254
|
+
Big-headline render. Designed to be a clean screenshot artifact:
|
|
255
|
+
bordered Panel with a heading, the tier callout up top, the spend
|
|
256
|
+
breakdown in the middle, the action line at the bottom, and the
|
|
257
|
+
share prompt OUTSIDE the panel.
|
|
258
|
+
"""
|
|
212
259
|
if sessions == 0:
|
|
213
260
|
console.print(
|
|
214
261
|
"\n[yellow]No usage data found.[/yellow]\n"
|
|
@@ -217,54 +264,96 @@ def _render(
|
|
|
217
264
|
)
|
|
218
265
|
return
|
|
219
266
|
|
|
220
|
-
#
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
267
|
+
# Build the inside-the-panel content as one rich Text/markup string.
|
|
268
|
+
# Rich Panel doesn't have native sub-spacing primitives, so we hand-pad
|
|
269
|
+
# with newlines to get the visual rhythm we want in the screenshot.
|
|
270
|
+
from rich.console import Group
|
|
271
|
+
from rich.panel import Panel
|
|
272
|
+
from rich.text import Text
|
|
273
|
+
from rich.align import Align
|
|
274
|
+
|
|
275
|
+
# Tier callout — the headline, plus a larger non-dim quip (no quotes)
|
|
276
|
+
# with `tj optimize` highlighted green-bold when it appears in the quip.
|
|
277
|
+
headline = Text()
|
|
278
|
+
headline.append(f"{tier.emoji} ", style="")
|
|
279
|
+
headline.append("You're a ", style="bold")
|
|
280
|
+
headline.append(tier.label, style="bold")
|
|
281
|
+
headline.append(".", style="bold")
|
|
282
|
+
|
|
283
|
+
quip_text = Text()
|
|
284
|
+
# Walk the quip and recolor any `tj optimize` backtick-wrapped token green.
|
|
285
|
+
# Rich doesn't auto-parse backticks, so we do it manually with split.
|
|
286
|
+
parts = tier.quip.split("`tj optimize`")
|
|
287
|
+
for i, p in enumerate(parts):
|
|
288
|
+
if p:
|
|
289
|
+
quip_text.append(p, style="")
|
|
290
|
+
if i < len(parts) - 1:
|
|
291
|
+
quip_text.append("tj optimize", style="bold green")
|
|
292
|
+
|
|
293
|
+
# The spend / multiplier block — same content as before but with the
|
|
294
|
+
# cleaner formatters and a slightly tighter line structure.
|
|
295
|
+
body = Text()
|
|
227
296
|
actual_label = f"last {window_days}d" if window_days < 30 else "last 30d"
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
f"across [bold]{sessions}[/bold] sessions "
|
|
237
|
-
f"(≈ [bold]{format_cost(monthly_spend)}/mo[/bold] at this rate)."
|
|
238
|
-
)
|
|
297
|
+
body.append(_fmt_spend(spend_usd), style="bold")
|
|
298
|
+
body.append(f" in {actual_label} across ")
|
|
299
|
+
body.append(str(sessions), style="bold")
|
|
300
|
+
body.append(" sessions.")
|
|
301
|
+
if window_days != 30:
|
|
302
|
+
body.append(" (≈ ", style="dim")
|
|
303
|
+
body.append(_fmt_spend(monthly_spend), style="bold")
|
|
304
|
+
body.append("/mo at this rate)", style="dim")
|
|
239
305
|
|
|
240
|
-
# Plan multiplier — the punchline.
|
|
241
306
|
if plan_info and multiplier:
|
|
242
307
|
plan_label, fee = plan_info
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
)
|
|
308
|
+
body.append("\nThat's ")
|
|
309
|
+
body.append(f"{multiplier:.1f}×", style="bold")
|
|
310
|
+
body.append(" your ")
|
|
311
|
+
body.append(plan_label, style="bold")
|
|
312
|
+
body.append(f" cost ({_fmt_fee(fee)}/mo flat).")
|
|
247
313
|
elif plan_info:
|
|
248
314
|
plan_label, _ = plan_info
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
#
|
|
315
|
+
body.append("\nPlan: ")
|
|
316
|
+
body.append(plan_label, style="bold")
|
|
317
|
+
body.append(".")
|
|
318
|
+
|
|
319
|
+
# Action line — savings recoverable, or fall through to "no obvious
|
|
320
|
+
# savings yet". `tj optimize` rendered green-bold either way so the
|
|
321
|
+
# eye lands on the verb.
|
|
322
|
+
action = Text("💡 ")
|
|
254
323
|
if savings_usd > 0:
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
)
|
|
324
|
+
action.append(_fmt_spend(savings_usd) + "/mo", style="bold")
|
|
325
|
+
action.append(" of that looks recoverable. Run ")
|
|
326
|
+
action.append("tj optimize", style="bold green")
|
|
327
|
+
action.append(" to see candidates.")
|
|
259
328
|
else:
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
329
|
+
action.append("No obvious savings flagged yet — run ")
|
|
330
|
+
action.append("tj optimize", style="bold green")
|
|
331
|
+
action.append(" for the full report once you have more data.")
|
|
332
|
+
|
|
333
|
+
# Compose with deliberate vertical spacing.
|
|
334
|
+
panel_body = Group(
|
|
335
|
+
headline,
|
|
336
|
+
Text(""), # blank line under headline
|
|
337
|
+
Align.left(quip_text),
|
|
338
|
+
Text(""), # blank line under quip
|
|
339
|
+
body,
|
|
340
|
+
Text(""), # blank line before action
|
|
341
|
+
action,
|
|
342
|
+
)
|
|
265
343
|
|
|
266
|
-
|
|
344
|
+
console.print()
|
|
345
|
+
console.print(Panel(
|
|
346
|
+
panel_body,
|
|
347
|
+
title="[bold]TokenJam TokenMaxxing Report[/bold]",
|
|
348
|
+
title_align="left",
|
|
349
|
+
border_style="dim",
|
|
350
|
+
padding=(1, 2),
|
|
351
|
+
))
|
|
352
|
+
|
|
353
|
+
# Share prompt — outside the panel, teal, points at the brand handle so
|
|
354
|
+
# the social mechanic routes to a real account we can amplify from.
|
|
267
355
|
console.print(
|
|
268
|
-
" [
|
|
356
|
+
" [cyan]Share your tier: screenshot the above and tag "
|
|
357
|
+
"[bold]@tokenjamdev[/bold][/cyan]"
|
|
269
358
|
)
|
|
270
359
|
console.print()
|
|
@@ -32,7 +32,7 @@ def calculate_cost(
|
|
|
32
32
|
if rates is None:
|
|
33
33
|
logger.warning(
|
|
34
34
|
"No pricing data for %s/%s — using default rates. "
|
|
35
|
-
"Add to pricing/models.toml to get accurate costs.",
|
|
35
|
+
"Add to tokenjam/pricing/models.toml to get accurate costs.",
|
|
36
36
|
provider, model,
|
|
37
37
|
)
|
|
38
38
|
rates = ModelRates(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# pricing/models.toml
|
|
1
|
+
# tokenjam/pricing/models.toml
|
|
2
2
|
# Prices in USD per million tokens.
|
|
3
3
|
# Submit a PR when provider prices change.
|
|
4
4
|
|
|
@@ -15,10 +15,10 @@ cache_read_per_mtok = 0.50
|
|
|
15
15
|
cache_write_per_mtok = 6.25
|
|
16
16
|
|
|
17
17
|
[anthropic.claude-opus-4-5]
|
|
18
|
-
input_per_mtok =
|
|
19
|
-
output_per_mtok =
|
|
20
|
-
cache_read_per_mtok =
|
|
21
|
-
cache_write_per_mtok =
|
|
18
|
+
input_per_mtok = 5.00
|
|
19
|
+
output_per_mtok = 25.00
|
|
20
|
+
cache_read_per_mtok = 0.50
|
|
21
|
+
cache_write_per_mtok = 6.25
|
|
22
22
|
|
|
23
23
|
[anthropic.claude-opus-4-6]
|
|
24
24
|
input_per_mtok = 5.00
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
# pricing/models.toml
|
|
2
|
-
# Prices in USD per million tokens.
|
|
3
|
-
# Submit a PR when provider prices change.
|
|
4
|
-
|
|
5
|
-
[anthropic.claude-opus-4-8]
|
|
6
|
-
input_per_mtok = 5.00
|
|
7
|
-
output_per_mtok = 25.00
|
|
8
|
-
cache_read_per_mtok = 0.50
|
|
9
|
-
cache_write_per_mtok = 6.25
|
|
10
|
-
|
|
11
|
-
[anthropic.claude-opus-4-7]
|
|
12
|
-
input_per_mtok = 5.00
|
|
13
|
-
output_per_mtok = 25.00
|
|
14
|
-
cache_read_per_mtok = 0.50
|
|
15
|
-
cache_write_per_mtok = 6.25
|
|
16
|
-
|
|
17
|
-
[anthropic.claude-opus-4-6]
|
|
18
|
-
input_per_mtok = 5.00
|
|
19
|
-
output_per_mtok = 25.00
|
|
20
|
-
cache_read_per_mtok = 0.50
|
|
21
|
-
cache_write_per_mtok = 6.25
|
|
22
|
-
|
|
23
|
-
[anthropic.claude-sonnet-4-6]
|
|
24
|
-
input_per_mtok = 3.00
|
|
25
|
-
output_per_mtok = 15.00
|
|
26
|
-
cache_read_per_mtok = 0.30
|
|
27
|
-
cache_write_per_mtok = 3.75
|
|
28
|
-
|
|
29
|
-
[anthropic.claude-haiku-4-5]
|
|
30
|
-
input_per_mtok = 0.80
|
|
31
|
-
output_per_mtok = 4.00
|
|
32
|
-
cache_read_per_mtok = 0.08
|
|
33
|
-
cache_write_per_mtok = 1.00
|
|
34
|
-
|
|
35
|
-
[openai.gpt-4o]
|
|
36
|
-
input_per_mtok = 2.50
|
|
37
|
-
output_per_mtok = 10.00
|
|
38
|
-
|
|
39
|
-
[openai.gpt-4o-mini]
|
|
40
|
-
input_per_mtok = 0.15
|
|
41
|
-
output_per_mtok = 0.60
|
|
42
|
-
|
|
43
|
-
[openai.o3]
|
|
44
|
-
input_per_mtok = 10.00
|
|
45
|
-
output_per_mtok = 40.00
|
|
46
|
-
|
|
47
|
-
[openai.o4-mini]
|
|
48
|
-
input_per_mtok = 1.10
|
|
49
|
-
output_per_mtok = 4.40
|
|
50
|
-
|
|
51
|
-
[google.gemini-2-5-pro]
|
|
52
|
-
input_per_mtok = 1.25
|
|
53
|
-
output_per_mtok = 10.00
|
|
54
|
-
|
|
55
|
-
[google.gemini-2-5-flash]
|
|
56
|
-
input_per_mtok = 0.15
|
|
57
|
-
output_per_mtok = 0.60
|
|
58
|
-
|
|
59
|
-
[aws.us-amazon-nova-pro-v1]
|
|
60
|
-
input_per_mtok = 0.80
|
|
61
|
-
output_per_mtok = 3.20
|
|
62
|
-
|
|
63
|
-
[aws.us-amazon-nova-lite-v1]
|
|
64
|
-
input_per_mtok = 0.06
|
|
65
|
-
output_per_mtok = 0.24
|
|
66
|
-
|
|
67
|
-
# OpenAI-compatible providers (Groq, Together, Fireworks, xAI, Azure OpenAI)
|
|
68
|
-
# use patch_openai() with a custom base_url — add their model names here
|
|
69
|
-
# as they are encountered.
|
|
70
|
-
|
|
71
|
-
[groq.llama-3-3-70b-versatile]
|
|
72
|
-
input_per_mtok = 0.59
|
|
73
|
-
output_per_mtok = 0.79
|
|
74
|
-
|
|
75
|
-
[xai.grok-3]
|
|
76
|
-
input_per_mtok = 3.00
|
|
77
|
-
output_per_mtok = 15.00
|
|
78
|
-
|
|
79
|
-
# HUD managed inference (inference.hud.ai)
|
|
80
|
-
[hud.claude-sonnet-4-5]
|
|
81
|
-
input_per_mtok = 3.00
|
|
82
|
-
output_per_mtok = 15.00
|
|
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
|