tokenpak 1.0.0__py3-none-any.whl
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.
- tokenpak/__init__.py +171 -0
- tokenpak/__main__.py +7 -0
- tokenpak/adapters/__init__.py +51 -0
- tokenpak/adapters/anthropic.py +258 -0
- tokenpak/adapters/base.py +212 -0
- tokenpak/adapters/langchain.py +174 -0
- tokenpak/adapters/litellm.py +192 -0
- tokenpak/adapters/openai.py +251 -0
- tokenpak/agent/__init__.py +1 -0
- tokenpak/agent/adapters/__init__.py +27 -0
- tokenpak/agent/adapters/base.py +59 -0
- tokenpak/agent/adapters/claude_cli.py +50 -0
- tokenpak/agent/adapters/generic.py +41 -0
- tokenpak/agent/adapters/openclaw.py +50 -0
- tokenpak/agent/adapters/registry.py +56 -0
- tokenpak/agent/agentic/__init__.py +154 -0
- tokenpak/agent/agentic/capabilities.py +269 -0
- tokenpak/agent/agentic/error_normalizer.py +156 -0
- tokenpak/agent/agentic/failure_memory.py +357 -0
- tokenpak/agent/agentic/handoff.py +517 -0
- tokenpak/agent/agentic/learning.py +894 -0
- tokenpak/agent/agentic/locks.py +248 -0
- tokenpak/agent/agentic/memory_promoter.py +304 -0
- tokenpak/agent/agentic/precondition_gates.py +432 -0
- tokenpak/agent/agentic/prefetcher.py +175 -0
- tokenpak/agent/agentic/proxy_workflow.py +167 -0
- tokenpak/agent/agentic/registry.py +211 -0
- tokenpak/agent/agentic/retry.py +528 -0
- tokenpak/agent/agentic/runbook_generator.py +446 -0
- tokenpak/agent/agentic/skill_compiler.py +360 -0
- tokenpak/agent/agentic/state_collector.py +389 -0
- tokenpak/agent/agentic/test_workflow_performance.py +360 -0
- tokenpak/agent/agentic/validation_framework.py +723 -0
- tokenpak/agent/agentic/workflow.py +457 -0
- tokenpak/agent/agentic/workflow_budget.py +363 -0
- tokenpak/agent/agentic/workflow_performance.py +285 -0
- tokenpak/agent/auth/__init__.py +0 -0
- tokenpak/agent/auth/cooldown_manager.py +246 -0
- tokenpak/agent/auth/oauth_manager.py +368 -0
- tokenpak/agent/cli/__init__.py +9 -0
- tokenpak/agent/cli/commands/__init__.py +23 -0
- tokenpak/agent/cli/commands/budget.py +633 -0
- tokenpak/agent/cli/commands/compliance.py +134 -0
- tokenpak/agent/cli/commands/compression.py +21 -0
- tokenpak/agent/cli/commands/config.py +123 -0
- tokenpak/agent/cli/commands/cost.py +403 -0
- tokenpak/agent/cli/commands/dashboard.py +412 -0
- tokenpak/agent/cli/commands/debug.py +52 -0
- tokenpak/agent/cli/commands/diff.py +303 -0
- tokenpak/agent/cli/commands/doctor.py +480 -0
- tokenpak/agent/cli/commands/exec.py +198 -0
- tokenpak/agent/cli/commands/fingerprint.py +209 -0
- tokenpak/agent/cli/commands/handoff.py +231 -0
- tokenpak/agent/cli/commands/help.py +324 -0
- tokenpak/agent/cli/commands/index.py +111 -0
- tokenpak/agent/cli/commands/last.py +119 -0
- tokenpak/agent/cli/commands/license.py +121 -0
- tokenpak/agent/cli/commands/maintenance.py +51 -0
- tokenpak/agent/cli/commands/metrics.py +233 -0
- tokenpak/agent/cli/commands/optimize.py +528 -0
- tokenpak/agent/cli/commands/policy.py +172 -0
- tokenpak/agent/cli/commands/preview.py +136 -0
- tokenpak/agent/cli/commands/prune.py +232 -0
- tokenpak/agent/cli/commands/replay.py +263 -0
- tokenpak/agent/cli/commands/retain.py +219 -0
- tokenpak/agent/cli/commands/route.py +36 -0
- tokenpak/agent/cli/commands/savings.py +319 -0
- tokenpak/agent/cli/commands/serve.py +99 -0
- tokenpak/agent/cli/commands/sla.py +147 -0
- tokenpak/agent/cli/commands/status.py +174 -0
- tokenpak/agent/cli/commands/teacher.py +68 -0
- tokenpak/agent/cli/commands/template.py +204 -0
- tokenpak/agent/cli/commands/trigger.py +7 -0
- tokenpak/agent/cli/commands/vault.py +32 -0
- tokenpak/agent/cli/commands/workflow.py +428 -0
- tokenpak/agent/cli/main.py +855 -0
- tokenpak/agent/cli/trigger_cmd.py +330 -0
- tokenpak/agent/compression/__init__.py +91 -0
- tokenpak/agent/compression/alias_compressor.py +309 -0
- tokenpak/agent/compression/canon.py +455 -0
- tokenpak/agent/compression/dedup.py +178 -0
- tokenpak/agent/compression/dictionary.py +307 -0
- tokenpak/agent/compression/directives.py +647 -0
- tokenpak/agent/compression/fidelity_tiers.py +545 -0
- tokenpak/agent/compression/instruction_table.py +219 -0
- tokenpak/agent/compression/pipeline.py +205 -0
- tokenpak/agent/compression/query_rewriter.py +365 -0
- tokenpak/agent/compression/recipes.py +637 -0
- tokenpak/agent/compression/salience/__init__.py +34 -0
- tokenpak/agent/compression/salience/code_extractor.py +269 -0
- tokenpak/agent/compression/salience/detect.py +70 -0
- tokenpak/agent/compression/salience/doc_extractor.py +147 -0
- tokenpak/agent/compression/salience/log_extractor.py +196 -0
- tokenpak/agent/compression/salience/router.py +128 -0
- tokenpak/agent/compression/schema_extractor.py +530 -0
- tokenpak/agent/compression/segmentizer.py +594 -0
- tokenpak/agent/compression/slot_filler.py +293 -0
- tokenpak/agent/config.py +166 -0
- tokenpak/agent/dashboard/__init__.py +6 -0
- tokenpak/agent/dashboard/export_api.py +120 -0
- tokenpak/agent/dashboard/export_csv.py +187 -0
- tokenpak/agent/dashboard/session_filter.py +260 -0
- tokenpak/agent/debug/__init__.py +6 -0
- tokenpak/agent/debug/logger.py +61 -0
- tokenpak/agent/debug/state.py +90 -0
- tokenpak/agent/fingerprint/__init__.py +22 -0
- tokenpak/agent/fingerprint/generator.py +207 -0
- tokenpak/agent/fingerprint/privacy.py +44 -0
- tokenpak/agent/fingerprint/sync.py +372 -0
- tokenpak/agent/ingest/__init__.py +37 -0
- tokenpak/agent/ingest/api.py +176 -0
- tokenpak/agent/ingest/claim_indexer.py +276 -0
- tokenpak/agent/ingest/cross_doc.py +784 -0
- tokenpak/agent/ingest/disclosure.py +183 -0
- tokenpak/agent/ingest/document_parser.py +861 -0
- tokenpak/agent/ingest/schema_converter.py +134 -0
- tokenpak/agent/ingest/table_extractor.py +464 -0
- tokenpak/agent/ingest/test_document_parser.py +400 -0
- tokenpak/agent/license/__init__.py +23 -0
- tokenpak/agent/license/activation.py +244 -0
- tokenpak/agent/license/admin_cli.py +267 -0
- tokenpak/agent/license/keys.py +188 -0
- tokenpak/agent/license/store.py +158 -0
- tokenpak/agent/license/validator.py +301 -0
- tokenpak/agent/macros/__init__.py +104 -0
- tokenpak/agent/macros/engine.py +550 -0
- tokenpak/agent/macros/hooks.py +454 -0
- tokenpak/agent/macros/premade_macros.py +225 -0
- tokenpak/agent/macros/scheduler.py +250 -0
- tokenpak/agent/macros/script_hooks.py +235 -0
- tokenpak/agent/memory/__init__.py +17 -0
- tokenpak/agent/memory/session_capsules.py +150 -0
- tokenpak/agent/proxy/__init__.py +35 -0
- tokenpak/agent/proxy/capsule_builder.py +80 -0
- tokenpak/agent/proxy/capsule_integration.py +242 -0
- tokenpak/agent/proxy/circuit_breaker.py +349 -0
- tokenpak/agent/proxy/connection_pool.py +448 -0
- tokenpak/agent/proxy/degradation.py +183 -0
- tokenpak/agent/proxy/example_selector.py +139 -0
- tokenpak/agent/proxy/failover.py +301 -0
- tokenpak/agent/proxy/failover_engine.py +642 -0
- tokenpak/agent/proxy/intent_policy.py +551 -0
- tokenpak/agent/proxy/oauth.py +274 -0
- tokenpak/agent/proxy/passthrough.py +273 -0
- tokenpak/agent/proxy/prompt_builder.py +1088 -0
- tokenpak/agent/proxy/providers/__init__.py +22 -0
- tokenpak/agent/proxy/providers/anthropic.py +229 -0
- tokenpak/agent/proxy/providers/detector.py +183 -0
- tokenpak/agent/proxy/providers/google.py +144 -0
- tokenpak/agent/proxy/providers/openai.py +164 -0
- tokenpak/agent/proxy/providers/stream_translator.py +540 -0
- tokenpak/agent/proxy/providers/translator.py +784 -0
- tokenpak/agent/proxy/proxy.py +62 -0
- tokenpak/agent/proxy/router.py +308 -0
- tokenpak/agent/proxy/server.py +1553 -0
- tokenpak/agent/proxy/server_async.py +1006 -0
- tokenpak/agent/proxy/startup.py +131 -0
- tokenpak/agent/proxy/stats.py +240 -0
- tokenpak/agent/proxy/stats_api.py +49 -0
- tokenpak/agent/proxy/streaming.py +181 -0
- tokenpak/agent/proxy/test_prompt_pack.py +352 -0
- tokenpak/agent/proxy/tool_schema_registry.py +242 -0
- tokenpak/agent/query/__init__.py +1 -0
- tokenpak/agent/query/api.py +408 -0
- tokenpak/agent/recipe_sdk.py +687 -0
- tokenpak/agent/regression/__init__.py +58 -0
- tokenpak/agent/regression/artifact_reuse.py +161 -0
- tokenpak/agent/regression/baseline_registry.py +95 -0
- tokenpak/agent/regression/classifier.py +119 -0
- tokenpak/agent/regression/delta_detector.py +189 -0
- tokenpak/agent/regression/feature_detector.py +168 -0
- tokenpak/agent/regression/retrieval_watchdog.py +437 -0
- tokenpak/agent/regression/stability_scorer.py +330 -0
- tokenpak/agent/regression/tests/test_classifier.py +86 -0
- tokenpak/agent/routing/__init__.py +1 -0
- tokenpak/agent/routing/fallback.py +248 -0
- tokenpak/agent/routing/rules.py +24 -0
- tokenpak/agent/semantic/__init__.py +38 -0
- tokenpak/agent/semantic/term_card_builder.py +492 -0
- tokenpak/agent/semantic/term_resolver.py +343 -0
- tokenpak/agent/semantic/test_proxy_integration.py +342 -0
- tokenpak/agent/semantic/test_term_resolver.py +438 -0
- tokenpak/agent/state_schemas/__init__.py +37 -0
- tokenpak/agent/teacher/__init__.py +5 -0
- tokenpak/agent/teacher/builder.py +217 -0
- tokenpak/agent/team/__init__.py +17 -0
- tokenpak/agent/team/agent_registry.py +223 -0
- tokenpak/agent/team/shared_vault.py +221 -0
- tokenpak/agent/team/templates.py +242 -0
- tokenpak/agent/telemetry/__init__.py +35 -0
- tokenpak/agent/telemetry/budget.py +389 -0
- tokenpak/agent/telemetry/collector.py +194 -0
- tokenpak/agent/telemetry/cost_tracker.py +256 -0
- tokenpak/agent/telemetry/demo.py +40 -0
- tokenpak/agent/telemetry/footer.py +103 -0
- tokenpak/agent/telemetry/replay.py +299 -0
- tokenpak/agent/telemetry/storage.py +154 -0
- tokenpak/agent/triggers/__init__.py +7 -0
- tokenpak/agent/triggers/daemon.py +192 -0
- tokenpak/agent/triggers/matcher.py +58 -0
- tokenpak/agent/triggers/store.py +103 -0
- tokenpak/agent/vault/__init__.py +27 -0
- tokenpak/agent/vault/ast_parser.py +245 -0
- tokenpak/agent/vault/blocks.py +290 -0
- tokenpak/agent/vault/chunk_shapes.py +376 -0
- tokenpak/agent/vault/indexer.py +202 -0
- tokenpak/agent/vault/retrieval.py +524 -0
- tokenpak/agent/vault/scoring.py +256 -0
- tokenpak/agent/vault/slicer.py +263 -0
- tokenpak/agent/vault/sqlite_retrieval.py +530 -0
- tokenpak/agent/vault/symbols.py +168 -0
- tokenpak/agent/vault/watcher.py +338 -0
- tokenpak/alerts.py +295 -0
- tokenpak/api/__init__.py +10 -0
- tokenpak/api/routes.py +185 -0
- tokenpak/artifact_store.py +431 -0
- tokenpak/assembler.py +337 -0
- tokenpak/attribution.py +263 -0
- tokenpak/benchmark.py +1405 -0
- tokenpak/broker.py +298 -0
- tokenpak/budget.py +90 -0
- tokenpak/budget_controller.py +149 -0
- tokenpak/budgeter.py +417 -0
- tokenpak/cache/__init__.py +72 -0
- tokenpak/cache/prefix_registry.py +224 -0
- tokenpak/cache/registry.py +121 -0
- tokenpak/cache/semantic_cache.py +341 -0
- tokenpak/cache/stable_cache.py +125 -0
- tokenpak/cache/telemetry.py +356 -0
- tokenpak/cache/volatile_cache.py +122 -0
- tokenpak/cache_report.py +50 -0
- tokenpak/calibration.py +187 -0
- tokenpak/calibrator.py +346 -0
- tokenpak/capsule/__init__.py +15 -0
- tokenpak/capsule/builder.py +362 -0
- tokenpak/citation_tracker.py +200 -0
- tokenpak/cli.py +5533 -0
- tokenpak/cli_doctor.py +198 -0
- tokenpak/compaction/__init__.py +54 -0
- tokenpak/compaction/modes.py +229 -0
- tokenpak/compaction/policy.py +282 -0
- tokenpak/compaction/topic_aware.py +345 -0
- tokenpak/compiler.py +207 -0
- tokenpak/complexity.py +309 -0
- tokenpak/config_loader.py +265 -0
- tokenpak/config_validator.py +294 -0
- tokenpak/connectors/__init__.py +42 -0
- tokenpak/connectors/base.py +95 -0
- tokenpak/connectors/base_source.py +81 -0
- tokenpak/connectors/git_adapter.py +110 -0
- tokenpak/connectors/github.py +174 -0
- tokenpak/connectors/google_drive.py +133 -0
- tokenpak/connectors/local.py +70 -0
- tokenpak/connectors/notion.py +181 -0
- tokenpak/connectors/notion_adapter.py +186 -0
- tokenpak/connectors/obsidian.py +109 -0
- tokenpak/connectors/url_adapter.py +153 -0
- tokenpak/context_composer.py +274 -0
- tokenpak/core.py +342 -0
- tokenpak/daily_report.py +212 -0
- tokenpak/dashboard/__init__.py +47 -0
- tokenpak/elo.py +164 -0
- tokenpak/engines/__init__.py +28 -0
- tokenpak/engines/base.py +42 -0
- tokenpak/engines/heuristic.py +45 -0
- tokenpak/engines/llmlingua.py +80 -0
- tokenpak/enterprise/__init__.py +10 -0
- tokenpak/enterprise/audit.py +413 -0
- tokenpak/enterprise/compliance.py +529 -0
- tokenpak/enterprise/governance.py +240 -0
- tokenpak/enterprise/policy.py +211 -0
- tokenpak/enterprise/sla.py +228 -0
- tokenpak/escalation.py +122 -0
- tokenpak/evidence_pack.py +236 -0
- tokenpak/extraction/__init__.py +13 -0
- tokenpak/extraction/extractor.py +161 -0
- tokenpak/extraction/models.py +88 -0
- tokenpak/extraction/patterns.py +41 -0
- tokenpak/fleet.py +282 -0
- tokenpak/formatting/__init__.py +4 -0
- tokenpak/formatting/colors.py +33 -0
- tokenpak/formatting/formatter.py +56 -0
- tokenpak/formatting/modes.py +19 -0
- tokenpak/formatting/symbols.py +19 -0
- tokenpak/goals.py +492 -0
- tokenpak/handlers/__init__.py +1 -0
- tokenpak/handlers/rate_limit.py +73 -0
- tokenpak/integrations/__init__.py +1 -0
- tokenpak/integrations/litellm/__init__.py +38 -0
- tokenpak/integrations/litellm/formatter.py +158 -0
- tokenpak/integrations/litellm/middleware.py +182 -0
- tokenpak/integrations/litellm/parser.py +76 -0
- tokenpak/integrations/litellm/proxy.py +156 -0
- tokenpak/intelligence/__init__.py +1 -0
- tokenpak/intelligence/ab_optimizer.py +631 -0
- tokenpak/intelligence/ab_router.py +244 -0
- tokenpak/intelligence/auth.py +313 -0
- tokenpak/intelligence/cost_intelligence.py +435 -0
- tokenpak/intelligence/cost_router.py +264 -0
- tokenpak/intelligence/deep_health.py +369 -0
- tokenpak/intelligence/license_endpoint.py +156 -0
- tokenpak/intelligence/server.py +366 -0
- tokenpak/intent_classifier.py +434 -0
- tokenpak/middleware/__init__.py +56 -0
- tokenpak/middleware/audit_trail.py +175 -0
- tokenpak/middleware/logger.py +237 -0
- tokenpak/middleware/logging_middleware.py +209 -0
- tokenpak/middleware/semantic_cache_middleware.py +153 -0
- tokenpak/middleware/tests/__init__.py +0 -0
- tokenpak/middleware/tests/conftest.py +10 -0
- tokenpak/middleware/tests/test_audit_trail.py +220 -0
- tokenpak/middleware/tests/test_logger.py +294 -0
- tokenpak/middleware/tests/test_logging_middleware.py +138 -0
- tokenpak/miss_detector.py +461 -0
- tokenpak/monitoring/__init__.py +21 -0
- tokenpak/monitoring/audit_trail.py +165 -0
- tokenpak/monitoring/health.py +217 -0
- tokenpak/monitoring/metrics.py +386 -0
- tokenpak/monitoring/request_logger.py +517 -0
- tokenpak/pack.py +455 -0
- tokenpak/post_run.py +268 -0
- tokenpak/precompute.py +634 -0
- tokenpak/pricing.py +143 -0
- tokenpak/processors/__init__.py +38 -0
- tokenpak/processors/code.py +474 -0
- tokenpak/processors/code_treesitter.py +532 -0
- tokenpak/processors/data.py +137 -0
- tokenpak/processors/text.py +195 -0
- tokenpak/profiles.py +129 -0
- tokenpak/proxy/__init__.py +3 -0
- tokenpak/proxy/adapters/__init__.py +34 -0
- tokenpak/proxy/adapters/anthropic_adapter.py +115 -0
- tokenpak/proxy/adapters/base.py +179 -0
- tokenpak/proxy/adapters/canonical.py +34 -0
- tokenpak/proxy/adapters/google_adapter.py +129 -0
- tokenpak/proxy/adapters/openai_chat_adapter.py +89 -0
- tokenpak/proxy/adapters/openai_responses_adapter.py +114 -0
- tokenpak/proxy/adapters/passthrough_adapter.py +76 -0
- tokenpak/proxy/adapters/registry.py +42 -0
- tokenpak/proxy/credential_passthrough.py +283 -0
- tokenpak/py.typed +0 -0
- tokenpak/reference_fetcher.py +151 -0
- tokenpak/reference_scanner.py +124 -0
- tokenpak/registry.py +326 -0
- tokenpak/report.py +299 -0
- tokenpak/request_audit.py +211 -0
- tokenpak/routing/__init__.py +3 -0
- tokenpak/routing/rules.py +359 -0
- tokenpak/routing_ledger.py +268 -0
- tokenpak/schemas/__init__.py +8 -0
- tokenpak/schemas/artifact.py +76 -0
- tokenpak/schemas/chunk.py +66 -0
- tokenpak/schemas/retrieval_cache.py +87 -0
- tokenpak/schemas/source_map.py +69 -0
- tokenpak/security.py +216 -0
- tokenpak/semantic/__init__.py +27 -0
- tokenpak/semantic/loader.py +213 -0
- tokenpak/semantic/resolver.py +292 -0
- tokenpak/shadow_hook.py +157 -0
- tokenpak/shadow_reader.py +558 -0
- tokenpak/span_extractor.py +227 -0
- tokenpak/state_manager.py +388 -0
- tokenpak/telemetry/__init__.py +35 -0
- tokenpak/telemetry/adapters/__init__.py +33 -0
- tokenpak/telemetry/adapters/anthropic.py +199 -0
- tokenpak/telemetry/adapters/base.py +93 -0
- tokenpak/telemetry/adapters/gemini.py +229 -0
- tokenpak/telemetry/adapters/openai.py +231 -0
- tokenpak/telemetry/adapters/registry.py +194 -0
- tokenpak/telemetry/anon_metrics.py +274 -0
- tokenpak/telemetry/api.py +189 -0
- tokenpak/telemetry/cache.py +229 -0
- tokenpak/telemetry/canonical.py +166 -0
- tokenpak/telemetry/collector.py +213 -0
- tokenpak/telemetry/config.py +108 -0
- tokenpak/telemetry/cost.py +659 -0
- tokenpak/telemetry/dashboard/__init__.py +8 -0
- tokenpak/telemetry/dashboard/dashboard.py +1053 -0
- tokenpak/telemetry/dashboard/pagination.py +127 -0
- tokenpak/telemetry/dashboard/query_builder.py +213 -0
- tokenpak/telemetry/event_schema.py +365 -0
- tokenpak/telemetry/insights.py +511 -0
- tokenpak/telemetry/integrity/__init__.py +0 -0
- tokenpak/telemetry/integrity/anomalies.py +353 -0
- tokenpak/telemetry/integrity/reconciliation.py +209 -0
- tokenpak/telemetry/integrity/validation.py +249 -0
- tokenpak/telemetry/milestones.py +391 -0
- tokenpak/telemetry/models.py +344 -0
- tokenpak/telemetry/operational/__init__.py +0 -0
- tokenpak/telemetry/operational/api.py +229 -0
- tokenpak/telemetry/operational/health.py +176 -0
- tokenpak/telemetry/operational/metrics.py +158 -0
- tokenpak/telemetry/operational/pruning.py +166 -0
- tokenpak/telemetry/pipeline.py +364 -0
- tokenpak/telemetry/pipeline_trace.py +106 -0
- tokenpak/telemetry/pricing.py +419 -0
- tokenpak/telemetry/prometheus.py +371 -0
- tokenpak/telemetry/proxy_trace_integration.py +170 -0
- tokenpak/telemetry/query.py +259 -0
- tokenpak/telemetry/query_models.py +50 -0
- tokenpak/telemetry/reporter.py +172 -0
- tokenpak/telemetry/response_models.py +259 -0
- tokenpak/telemetry/rollups.py +641 -0
- tokenpak/telemetry/segmentizer.py +1020 -0
- tokenpak/telemetry/server.py +1055 -0
- tokenpak/telemetry/settings.py +160 -0
- tokenpak/telemetry/stats.py +178 -0
- tokenpak/telemetry/storage.py +1314 -0
- tokenpak/telemetry/storage_base.py +314 -0
- tokenpak/telemetry/storage_events.py +231 -0
- tokenpak/telemetry/storage_rollups.py +340 -0
- tokenpak/telemetry/storage_segments.py +88 -0
- tokenpak/telemetry/storage_usage.py +361 -0
- tokenpak/tests/test_config_validation.py +472 -0
- tokenpak/tests/test_config_validation_simple.py +205 -0
- tokenpak/tests/test_config_validator_edge_cases.py +350 -0
- tokenpak/tests/test_cost_cache_module.py +606 -0
- tokenpak/tests/test_cost_integration.py +420 -0
- tokenpak/tests/test_proxy_integration.py +88 -0
- tokenpak/tests/test_proxy_module.py +359 -0
- tokenpak/tests/test_routing_integration.py +23 -0
- tokenpak/tests/test_savings_display.py +249 -0
- tokenpak/tests/test_schemas_integration.py +79 -0
- tokenpak/tests/test_telemetry_integration.py +278 -0
- tokenpak/tests/test_validation_gate_integration.py +382 -0
- tokenpak/timeline.py +206 -0
- tokenpak/tokens.py +134 -0
- tokenpak/trace.py +404 -0
- tokenpak/user_templates.py +199 -0
- tokenpak/validation/__init__.py +63 -0
- tokenpak/validation/frontmatter.py +120 -0
- tokenpak/validation/request_schema.py +289 -0
- tokenpak/validation/request_validator.py +620 -0
- tokenpak/validation/response_schema.py +120 -0
- tokenpak/validation/validator.py +326 -0
- tokenpak/validation_gate.py +145 -0
- tokenpak/validator.py +470 -0
- tokenpak/vendor_classifier.py +166 -0
- tokenpak/version_check.py +147 -0
- tokenpak/walker.py +82 -0
- tokenpak/watchdog.py +329 -0
- tokenpak/wire.py +62 -0
- tokenpak/workflow_performance.py +321 -0
- tokenpak-1.0.0.dist-info/METADATA +517 -0
- tokenpak-1.0.0.dist-info/RECORD +450 -0
- tokenpak-1.0.0.dist-info/WHEEL +5 -0
- tokenpak-1.0.0.dist-info/entry_points.txt +3 -0
- tokenpak-1.0.0.dist-info/licenses/LICENSE +21 -0
- tokenpak-1.0.0.dist-info/licenses/LICENSE_COMMERCIAL.md +108 -0
- tokenpak-1.0.0.dist-info/top_level.txt +1 -0
tokenpak/__init__.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""TokenPak — Universal Content Compiler for LLMs.
|
|
3
|
+
|
|
4
|
+
Public API surface for TokenPak v1.0.0.
|
|
5
|
+
Formalizes importable classes for agent integrations, deployment, and testing.
|
|
6
|
+
|
|
7
|
+
Quick start:
|
|
8
|
+
from tokenpak import TelemetryCollector, CacheManager, CompressionEngine, Budgeter
|
|
9
|
+
|
|
10
|
+
Sub-package imports:
|
|
11
|
+
from tokenpak.telemetry import TelemetryCollector
|
|
12
|
+
from tokenpak.engines import CompactionEngine, HeuristicEngine
|
|
13
|
+
from tokenpak.registry import Block, BlockRegistry
|
|
14
|
+
from tokenpak.budgeter import Budgeter
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
__version__ = "1.0.0"
|
|
20
|
+
__author__ = "Kevin Yang"
|
|
21
|
+
__license__ = "MIT"
|
|
22
|
+
__description__ = "Deterministic compression for multi-agent AI workflows"
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# Telemetry
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# Sub-packages (for advanced use)
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
from tokenpak import agent, connectors, proxy
|
|
31
|
+
|
|
32
|
+
# CompletionTracker: tracks per-completion cost, model, and latency
|
|
33
|
+
from tokenpak.agent.telemetry.cost_tracker import CostTracker as CompletionTracker
|
|
34
|
+
from tokenpak.budget import BudgetBlock
|
|
35
|
+
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
# Budgeting
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
from tokenpak.budgeter import Budgeter
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# CLI
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
from tokenpak.cli import main
|
|
45
|
+
from tokenpak.engines import get_engine
|
|
46
|
+
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
# Compression / Compaction Engines
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
# CompressionEngine: abstract base for all compaction strategies
|
|
51
|
+
from tokenpak.engines.base import CompactionEngine as CompressionEngine
|
|
52
|
+
from tokenpak.engines.heuristic import HeuristicEngine
|
|
53
|
+
from tokenpak.pack import CompiledResult, ContextPack, PackBlock, pack_prompt
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# Content Blocks
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
from tokenpak.registry import Block, BlockRegistry
|
|
59
|
+
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
# Compile Reports
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
from tokenpak.report import Action, CompileReport, Decision
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
# Cache
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
# CacheManager: semantic cache store (get/set/hit-rate tracking)
|
|
69
|
+
from tokenpak.telemetry.cache import CacheStore as CacheManager
|
|
70
|
+
from tokenpak.telemetry.collector import TelemetryCollector
|
|
71
|
+
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
# Token Counting (Level 1 — single import, zero config)
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
from tokenpak.tokens import count_tokens
|
|
76
|
+
from tokenpak.trace import ( # noqa: F401
|
|
77
|
+
TokenPakTrace,
|
|
78
|
+
TraceBuilder,
|
|
79
|
+
attach_trace_header,
|
|
80
|
+
attach_trace_envelope,
|
|
81
|
+
strip_trace,
|
|
82
|
+
strip_trace_header,
|
|
83
|
+
read_trace_header,
|
|
84
|
+
read_trace_envelope,
|
|
85
|
+
assert_no_leak,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
# Agent Handoff Protocol
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
from tokenpak.agent.agentic.handoff import (
|
|
93
|
+
HandoffBlock,
|
|
94
|
+
HandoffManager,
|
|
95
|
+
HandoffStatus,
|
|
96
|
+
HandoffWire as Handoff,
|
|
97
|
+
ContextRef,
|
|
98
|
+
TokenPak,
|
|
99
|
+
)
|
|
100
|
+
# ---------------------------------------------------------------------------
|
|
101
|
+
# Agentic handoff protocol
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
from tokenpak.agent.agentic.handoff import (
|
|
104
|
+
ContextRef,
|
|
105
|
+
HandoffBlock,
|
|
106
|
+
HandoffManager,
|
|
107
|
+
HandoffStatus,
|
|
108
|
+
HandoffWire,
|
|
109
|
+
TokenPak,
|
|
110
|
+
)
|
|
111
|
+
# HandoffWire is the intended top-level "Handoff" API (pack-based wire format)
|
|
112
|
+
# The internal Handoff dataclass (file-based) is available via
|
|
113
|
+
# tokenpak.agent.agentic.handoff.Handoff
|
|
114
|
+
Handoff = HandoffWire # type: ignore
|
|
115
|
+
|
|
116
|
+
# ---------------------------------------------------------------------------
|
|
117
|
+
# Public API declaration
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
__all__ = [
|
|
120
|
+
# Metadata
|
|
121
|
+
"__version__",
|
|
122
|
+
"__author__",
|
|
123
|
+
"__license__",
|
|
124
|
+
"__description__",
|
|
125
|
+
# Telemetry
|
|
126
|
+
"TelemetryCollector",
|
|
127
|
+
"CompletionTracker",
|
|
128
|
+
# Cache
|
|
129
|
+
"CacheManager",
|
|
130
|
+
# Compression
|
|
131
|
+
"CompressionEngine",
|
|
132
|
+
"HeuristicEngine",
|
|
133
|
+
"get_engine",
|
|
134
|
+
# Content Blocks
|
|
135
|
+
"Block",
|
|
136
|
+
"BlockRegistry",
|
|
137
|
+
# Budgeting
|
|
138
|
+
"Budgeter",
|
|
139
|
+
"BudgetBlock",
|
|
140
|
+
# Compile Reports
|
|
141
|
+
"Action",
|
|
142
|
+
"CompileReport",
|
|
143
|
+
"Decision",
|
|
144
|
+
"ContextPack",
|
|
145
|
+
"PackBlock",
|
|
146
|
+
"CompiledResult",
|
|
147
|
+
# Incremental adoption helpers
|
|
148
|
+
"count_tokens",
|
|
149
|
+
"pack_prompt",
|
|
150
|
+
# Agent Handoff Protocol
|
|
151
|
+
"HandoffBlock",
|
|
152
|
+
"HandoffManager",
|
|
153
|
+
"HandoffStatus",
|
|
154
|
+
"Handoff",
|
|
155
|
+
"ContextRef",
|
|
156
|
+
"TokenPak",
|
|
157
|
+
# CLI
|
|
158
|
+
"main",
|
|
159
|
+
# Sub-packages
|
|
160
|
+
"connectors",
|
|
161
|
+
"agent",
|
|
162
|
+
"proxy",
|
|
163
|
+
# Agentic handoff protocol
|
|
164
|
+
"ContextRef",
|
|
165
|
+
"Handoff",
|
|
166
|
+
"HandoffBlock",
|
|
167
|
+
"HandoffManager",
|
|
168
|
+
"HandoffStatus",
|
|
169
|
+
"HandoffWire",
|
|
170
|
+
"TokenPak",
|
|
171
|
+
]
|
tokenpak/__main__.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tokenpak.adapters — Unified SDK/framework adapter layer.
|
|
3
|
+
|
|
4
|
+
All adapters share the ``TokenPakAdapter`` base contract:
|
|
5
|
+
|
|
6
|
+
prepare_request(request) → normalised proxy dict
|
|
7
|
+
send(prepared_request) → raw proxy response dict
|
|
8
|
+
parse_response(response) → provider-native response dict
|
|
9
|
+
extract_tokens(response) → token-usage summary dict
|
|
10
|
+
|
|
11
|
+
Quick start
|
|
12
|
+
-----------
|
|
13
|
+
>>> from tokenpak.adapters import AnthropicAdapter, OpenAIAdapter
|
|
14
|
+
>>> adapter = OpenAIAdapter(base_url="http://127.0.0.1:8767", api_key="sk-...")
|
|
15
|
+
>>> response = adapter.call({"model": "gpt-4o", "messages": [...]})
|
|
16
|
+
>>> usage = adapter.extract_tokens(response)
|
|
17
|
+
|
|
18
|
+
See also
|
|
19
|
+
--------
|
|
20
|
+
- ``tokenpak.adapters.base`` — abstract base + exception hierarchy
|
|
21
|
+
- ``tokenpak.adapters.anthropic`` — Anthropic Messages API
|
|
22
|
+
- ``tokenpak.adapters.openai`` — OpenAI Chat Completions API
|
|
23
|
+
- ``tokenpak.adapters.langchain`` — LangChain (ChatOpenAI / ChatAnthropic)
|
|
24
|
+
- ``tokenpak.adapters.litellm`` — LiteLLM provider-agnostic routing
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from tokenpak.adapters.anthropic import AnthropicAdapter
|
|
28
|
+
from tokenpak.adapters.base import (
|
|
29
|
+
TokenPakAdapter,
|
|
30
|
+
TokenPakAdapterError,
|
|
31
|
+
TokenPakAuthError,
|
|
32
|
+
TokenPakConfigError,
|
|
33
|
+
TokenPakTimeoutError,
|
|
34
|
+
)
|
|
35
|
+
from tokenpak.adapters.langchain import LangChainAdapter
|
|
36
|
+
from tokenpak.adapters.litellm import LiteLLMAdapter
|
|
37
|
+
from tokenpak.adapters.openai import OpenAIAdapter
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
# Base
|
|
41
|
+
"TokenPakAdapter",
|
|
42
|
+
"TokenPakAdapterError",
|
|
43
|
+
"TokenPakAuthError",
|
|
44
|
+
"TokenPakConfigError",
|
|
45
|
+
"TokenPakTimeoutError",
|
|
46
|
+
# Concrete adapters
|
|
47
|
+
"AnthropicAdapter",
|
|
48
|
+
"OpenAIAdapter",
|
|
49
|
+
"LangChainAdapter",
|
|
50
|
+
"LiteLLMAdapter",
|
|
51
|
+
]
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TokenPak Anthropic SDK Adapter
|
|
3
|
+
|
|
4
|
+
Routes Anthropic ``messages.create`` requests through the TokenPak proxy.
|
|
5
|
+
Preserves the full Anthropic Messages API shape on both request and response
|
|
6
|
+
so callers require zero code changes beyond swapping in this adapter.
|
|
7
|
+
|
|
8
|
+
Request format handled:
|
|
9
|
+
{
|
|
10
|
+
"model": "claude-3-5-sonnet-20241022",
|
|
11
|
+
"max_tokens": 4096,
|
|
12
|
+
"system": "...",
|
|
13
|
+
"messages": [{"role": "user", "content": "..."}],
|
|
14
|
+
...
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
Token extraction
|
|
18
|
+
----------------
|
|
19
|
+
Anthropic reports exact usage in every non-streaming response:
|
|
20
|
+
``usage.input_tokens``, ``usage.output_tokens``,
|
|
21
|
+
``usage.cache_read_input_tokens``, ``usage.cache_creation_input_tokens``
|
|
22
|
+
|
|
23
|
+
Error handling
|
|
24
|
+
--------------
|
|
25
|
+
- 401/403 → TokenPakAuthError
|
|
26
|
+
- timeout → TokenPakTimeoutError
|
|
27
|
+
- provider error block in response → TokenPakAdapterError
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import json
|
|
33
|
+
import logging
|
|
34
|
+
import time
|
|
35
|
+
from typing import Any
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
import requests as _requests
|
|
39
|
+
_REQUESTS_AVAILABLE = True
|
|
40
|
+
except ImportError:
|
|
41
|
+
_REQUESTS_AVAILABLE = False
|
|
42
|
+
|
|
43
|
+
from tokenpak.adapters.base import (
|
|
44
|
+
TokenPakAdapter,
|
|
45
|
+
TokenPakAdapterError,
|
|
46
|
+
TokenPakAuthError,
|
|
47
|
+
TokenPakConfigError,
|
|
48
|
+
TokenPakTimeoutError,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
_log = logging.getLogger("tokenpak.adapters.anthropic")
|
|
52
|
+
|
|
53
|
+
# Required fields in every request
|
|
54
|
+
_REQUIRED_FIELDS = frozenset({"model", "messages", "max_tokens"})
|
|
55
|
+
|
|
56
|
+
# Anthropic stop_reason → canonical finish_reason
|
|
57
|
+
_STOP_REASON_MAP: dict[str, str] = {
|
|
58
|
+
"end_turn": "stop",
|
|
59
|
+
"max_tokens": "max_tokens",
|
|
60
|
+
"stop_sequence": "stop_sequence",
|
|
61
|
+
"tool_use": "tool_use",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class AnthropicAdapter(TokenPakAdapter):
|
|
66
|
+
"""TokenPak adapter for the Anthropic Messages API.
|
|
67
|
+
|
|
68
|
+
Usage
|
|
69
|
+
-----
|
|
70
|
+
>>> adapter = AnthropicAdapter(
|
|
71
|
+
... base_url="http://127.0.0.1:8767",
|
|
72
|
+
... api_key="sk-ant-...",
|
|
73
|
+
... )
|
|
74
|
+
>>> response = adapter.call({
|
|
75
|
+
... "model": "claude-3-5-sonnet-20241022",
|
|
76
|
+
... "max_tokens": 1024,
|
|
77
|
+
... "messages": [{"role": "user", "content": "Hello"}],
|
|
78
|
+
... })
|
|
79
|
+
>>> tokens = adapter.extract_tokens(response)
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
provider_name: str = "anthropic"
|
|
83
|
+
|
|
84
|
+
PROXY_PATH: str = "/v1/messages"
|
|
85
|
+
ANTHROPIC_VERSION: str = "2023-06-01"
|
|
86
|
+
|
|
87
|
+
# ── prepare_request ───────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
def prepare_request(self, request: dict[str, Any]) -> dict[str, Any]:
|
|
90
|
+
"""Validate and normalise an Anthropic request.
|
|
91
|
+
|
|
92
|
+
Validation:
|
|
93
|
+
- ``model``, ``messages``, ``max_tokens`` must be present
|
|
94
|
+
- ``messages`` must be a non-empty list
|
|
95
|
+
- Each message must have ``role`` (str) and ``content`` (str | list)
|
|
96
|
+
|
|
97
|
+
Normalisation:
|
|
98
|
+
- Strips unknown top-level keys that would be rejected by the proxy
|
|
99
|
+
(conservative: passes through everything; proxy decides)
|
|
100
|
+
- Adds ``stream: false`` default if not specified
|
|
101
|
+
"""
|
|
102
|
+
missing = _REQUIRED_FIELDS - request.keys()
|
|
103
|
+
if missing:
|
|
104
|
+
raise TokenPakConfigError(
|
|
105
|
+
f"AnthropicAdapter.prepare_request: missing required fields: {sorted(missing)}"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
messages = request.get("messages")
|
|
109
|
+
if not isinstance(messages, list) or not messages:
|
|
110
|
+
raise TokenPakConfigError(
|
|
111
|
+
"AnthropicAdapter.prepare_request: 'messages' must be a non-empty list."
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
for i, msg in enumerate(messages):
|
|
115
|
+
if not isinstance(msg, dict):
|
|
116
|
+
raise TokenPakConfigError(
|
|
117
|
+
f"AnthropicAdapter.prepare_request: messages[{i}] must be a dict."
|
|
118
|
+
)
|
|
119
|
+
if "role" not in msg or "content" not in msg:
|
|
120
|
+
raise TokenPakConfigError(
|
|
121
|
+
f"AnthropicAdapter.prepare_request: messages[{i}] must have 'role' and 'content'."
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
prepared = dict(request)
|
|
125
|
+
prepared.setdefault("stream", False)
|
|
126
|
+
|
|
127
|
+
self.logger.debug(
|
|
128
|
+
"prepare_request model=%s messages=%d",
|
|
129
|
+
prepared.get("model"),
|
|
130
|
+
len(messages),
|
|
131
|
+
)
|
|
132
|
+
return prepared
|
|
133
|
+
|
|
134
|
+
# ── send ──────────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
def send(self, prepared_request: dict[str, Any]) -> dict[str, Any]:
|
|
137
|
+
"""POST to ``{base_url}/v1/messages`` through the proxy."""
|
|
138
|
+
if not _REQUESTS_AVAILABLE:
|
|
139
|
+
raise TokenPakAdapterError(
|
|
140
|
+
"AnthropicAdapter.send: 'requests' package is required. "
|
|
141
|
+
"Install with: pip install requests"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
url = f"{self.base_url}{self.PROXY_PATH}"
|
|
145
|
+
headers = {
|
|
146
|
+
"Content-Type": "application/json",
|
|
147
|
+
"x-api-key": self.api_key,
|
|
148
|
+
"anthropic-version": self.ANTHROPIC_VERSION,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
self.logger.debug("send POST %s model=%s", url, prepared_request.get("model"))
|
|
152
|
+
t0 = time.monotonic()
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
resp = _requests.post(
|
|
156
|
+
url,
|
|
157
|
+
headers=headers,
|
|
158
|
+
data=json.dumps(prepared_request, ensure_ascii=False).encode("utf-8"),
|
|
159
|
+
timeout=self.timeout_s,
|
|
160
|
+
)
|
|
161
|
+
except _requests.exceptions.Timeout as exc:
|
|
162
|
+
raise TokenPakTimeoutError(
|
|
163
|
+
f"AnthropicAdapter.send: request timed out after {self.timeout_s}s."
|
|
164
|
+
) from exc
|
|
165
|
+
except _requests.exceptions.RequestException as exc:
|
|
166
|
+
raise TokenPakAdapterError(
|
|
167
|
+
f"AnthropicAdapter.send: HTTP transport error: {exc}"
|
|
168
|
+
) from exc
|
|
169
|
+
|
|
170
|
+
elapsed_ms = (time.monotonic() - t0) * 1000
|
|
171
|
+
self.logger.info("send complete status=%d elapsed_ms=%.1f", resp.status_code, elapsed_ms)
|
|
172
|
+
|
|
173
|
+
if resp.status_code in (401, 403):
|
|
174
|
+
raise TokenPakAuthError(
|
|
175
|
+
f"AnthropicAdapter.send: authentication failed (HTTP {resp.status_code}).",
|
|
176
|
+
status_code=resp.status_code,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
if not resp.ok:
|
|
180
|
+
try:
|
|
181
|
+
err_body = resp.json()
|
|
182
|
+
except Exception:
|
|
183
|
+
err_body = resp.text
|
|
184
|
+
raise TokenPakAdapterError(
|
|
185
|
+
f"AnthropicAdapter.send: proxy returned HTTP {resp.status_code}.",
|
|
186
|
+
status_code=resp.status_code,
|
|
187
|
+
raw=err_body,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
return resp.json()
|
|
192
|
+
except Exception as exc:
|
|
193
|
+
raise TokenPakAdapterError(
|
|
194
|
+
"AnthropicAdapter.send: response body is not valid JSON."
|
|
195
|
+
) from exc
|
|
196
|
+
|
|
197
|
+
# ── parse_response ────────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
def parse_response(self, response: dict[str, Any]) -> dict[str, Any]:
|
|
200
|
+
"""Validate proxy response and surface provider errors.
|
|
201
|
+
|
|
202
|
+
Returns the response unchanged if valid; raises ``TokenPakAdapterError``
|
|
203
|
+
when the response contains an Anthropic error block.
|
|
204
|
+
"""
|
|
205
|
+
error = response.get("error")
|
|
206
|
+
if error:
|
|
207
|
+
if isinstance(error, dict):
|
|
208
|
+
msg = error.get("message", str(error))
|
|
209
|
+
err_type = error.get("type", "unknown")
|
|
210
|
+
else:
|
|
211
|
+
msg, err_type = str(error), "unknown"
|
|
212
|
+
raise TokenPakAdapterError(
|
|
213
|
+
f"AnthropicAdapter.parse_response: provider error [{err_type}]: {msg}",
|
|
214
|
+
raw=response,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
if "content" not in response and "type" not in response:
|
|
218
|
+
self.logger.warning(
|
|
219
|
+
"parse_response: response missing 'content' and 'type' — "
|
|
220
|
+
"may be malformed: %s",
|
|
221
|
+
list(response.keys()),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
return response
|
|
225
|
+
|
|
226
|
+
# ── extract_tokens ────────────────────────────────────────────────────
|
|
227
|
+
|
|
228
|
+
def extract_tokens(self, response: dict[str, Any]) -> dict[str, Any]:
|
|
229
|
+
"""Extract Anthropic usage block.
|
|
230
|
+
|
|
231
|
+
Returns zeros with a warning if ``usage`` is absent (e.g. streaming
|
|
232
|
+
partial chunks where billing data hasn't arrived yet).
|
|
233
|
+
"""
|
|
234
|
+
usage = response.get("usage", {})
|
|
235
|
+
if not usage:
|
|
236
|
+
self.logger.warning(
|
|
237
|
+
"extract_tokens: no 'usage' block in response — returning zeros."
|
|
238
|
+
)
|
|
239
|
+
return {
|
|
240
|
+
"input_tokens": 0,
|
|
241
|
+
"output_tokens": 0,
|
|
242
|
+
"cache_read": 0,
|
|
243
|
+
"cache_write": 0,
|
|
244
|
+
"total": 0,
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
input_tokens = int(usage.get("input_tokens", 0))
|
|
248
|
+
output_tokens = int(usage.get("output_tokens", 0))
|
|
249
|
+
cache_read = int(usage.get("cache_read_input_tokens", 0))
|
|
250
|
+
cache_write = int(usage.get("cache_creation_input_tokens", 0))
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
"input_tokens": input_tokens,
|
|
254
|
+
"output_tokens": output_tokens,
|
|
255
|
+
"cache_read": cache_read,
|
|
256
|
+
"cache_write": cache_write,
|
|
257
|
+
"total": input_tokens + output_tokens,
|
|
258
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TokenPak Unified Adapter Base — TokenPakAdapter
|
|
3
|
+
|
|
4
|
+
All SDK/framework adapters (Anthropic, OpenAI, LangChain, LiteLLM, etc.)
|
|
5
|
+
inherit from this class and implement the four lifecycle hooks.
|
|
6
|
+
|
|
7
|
+
Lifecycle
|
|
8
|
+
---------
|
|
9
|
+
1. ``prepare_request(request)`` — validate & normalise SDK request → proxy format
|
|
10
|
+
2. ``send(prepared_request)`` — POST to TokenPak proxy, handle errors/timeouts
|
|
11
|
+
3. ``parse_response(response)`` — proxy response → SDK-native format
|
|
12
|
+
4. ``extract_tokens(response)`` — pull token-usage counts for budgeting
|
|
13
|
+
|
|
14
|
+
Error Handling Contract
|
|
15
|
+
-----------------------
|
|
16
|
+
All concrete adapters MUST:
|
|
17
|
+
- Wrap HTTP errors in ``TokenPakAdapterError``
|
|
18
|
+
- Wrap timeout errors in ``TokenPakTimeoutError``
|
|
19
|
+
- Wrap auth/config errors in ``TokenPakConfigError``
|
|
20
|
+
- Never raise bare ``requests.exceptions.*`` or provider SDK exceptions
|
|
21
|
+
directly — always translate to the canonical hierarchy.
|
|
22
|
+
|
|
23
|
+
Logging
|
|
24
|
+
-------
|
|
25
|
+
Use ``self.logger`` (standard ``logging.Logger``) for all log output.
|
|
26
|
+
Log levels:
|
|
27
|
+
- DEBUG : request/response body summaries (no credentials)
|
|
28
|
+
- INFO : round-trip timings, cache-hit notices
|
|
29
|
+
- WARNING : retried requests, degraded fallbacks
|
|
30
|
+
- ERROR : terminal failures before raising exceptions
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import logging
|
|
36
|
+
import time
|
|
37
|
+
from abc import ABC, abstractmethod
|
|
38
|
+
from typing import Any
|
|
39
|
+
|
|
40
|
+
# ── Canonical exception hierarchy ─────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
class TokenPakAdapterError(Exception):
|
|
43
|
+
"""Base exception for all TokenPak adapter errors."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, message: str, status_code: int | None = None, raw: Any = None) -> None:
|
|
46
|
+
super().__init__(message)
|
|
47
|
+
self.status_code = status_code
|
|
48
|
+
self.raw = raw
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class TokenPakTimeoutError(TokenPakAdapterError):
|
|
52
|
+
"""Raised when the proxy does not respond within ``timeout_s`` seconds."""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class TokenPakConfigError(TokenPakAdapterError):
|
|
56
|
+
"""Raised when required configuration (base_url, api_key) is missing/invalid."""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TokenPakAuthError(TokenPakAdapterError):
|
|
60
|
+
"""Raised on 401/403 responses from the proxy."""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# ── Base adapter ───────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
class TokenPakAdapter(ABC):
|
|
66
|
+
"""Abstract base class for all TokenPak SDK/framework adapters.
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
base_url:
|
|
71
|
+
TokenPak proxy endpoint, e.g. ``"http://127.0.0.1:8767"``.
|
|
72
|
+
Must not have a trailing slash.
|
|
73
|
+
api_key:
|
|
74
|
+
Provider API key forwarded transparently through the proxy.
|
|
75
|
+
timeout_s:
|
|
76
|
+
Request timeout in seconds. Defaults to 120.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
#: Subclasses set this to a stable, lowercase identifier (e.g. "anthropic").
|
|
80
|
+
provider_name: str = "unknown"
|
|
81
|
+
|
|
82
|
+
DEFAULT_TIMEOUT_S: float = 120.0
|
|
83
|
+
|
|
84
|
+
def __init__(self, base_url: str, api_key: str, timeout_s: float | None = None) -> None:
|
|
85
|
+
if not base_url:
|
|
86
|
+
raise TokenPakConfigError("base_url must not be empty.")
|
|
87
|
+
if not api_key:
|
|
88
|
+
raise TokenPakConfigError("api_key must not be empty.")
|
|
89
|
+
|
|
90
|
+
self.base_url = base_url.rstrip("/")
|
|
91
|
+
self.api_key = api_key
|
|
92
|
+
self.timeout_s = timeout_s if timeout_s is not None else self.DEFAULT_TIMEOUT_S
|
|
93
|
+
self.logger = logging.getLogger(f"tokenpak.adapters.{self.provider_name}")
|
|
94
|
+
|
|
95
|
+
# ── Lifecycle hooks ────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
@abstractmethod
|
|
98
|
+
def prepare_request(self, request: dict[str, Any]) -> dict[str, Any]:
|
|
99
|
+
"""Validate and normalise an SDK request dict into proxy format.
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
request:
|
|
104
|
+
Raw dict as the SDK caller would build it (model, messages, etc.).
|
|
105
|
+
|
|
106
|
+
Returns
|
|
107
|
+
-------
|
|
108
|
+
dict
|
|
109
|
+
Proxy-ready request dict. Must contain at least ``"model"``
|
|
110
|
+
and ``"messages"`` (or the provider's equivalent).
|
|
111
|
+
|
|
112
|
+
Raises
|
|
113
|
+
------
|
|
114
|
+
TokenPakConfigError
|
|
115
|
+
If required fields are missing or have invalid types.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
@abstractmethod
|
|
119
|
+
def send(self, prepared_request: dict[str, Any]) -> dict[str, Any]:
|
|
120
|
+
"""POST *prepared_request* to the TokenPak proxy and return the response.
|
|
121
|
+
|
|
122
|
+
Parameters
|
|
123
|
+
----------
|
|
124
|
+
prepared_request:
|
|
125
|
+
The dict returned by ``prepare_request``.
|
|
126
|
+
|
|
127
|
+
Returns
|
|
128
|
+
-------
|
|
129
|
+
dict
|
|
130
|
+
Raw response dict from the proxy.
|
|
131
|
+
|
|
132
|
+
Raises
|
|
133
|
+
------
|
|
134
|
+
TokenPakTimeoutError
|
|
135
|
+
If the request exceeds ``self.timeout_s``.
|
|
136
|
+
TokenPakAuthError
|
|
137
|
+
On 401/403 responses.
|
|
138
|
+
TokenPakAdapterError
|
|
139
|
+
For all other HTTP/transport errors.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
@abstractmethod
|
|
143
|
+
def parse_response(self, response: dict[str, Any]) -> dict[str, Any]:
|
|
144
|
+
"""Convert a raw proxy response into the provider's native SDK format.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
response:
|
|
149
|
+
Raw dict returned by ``send``.
|
|
150
|
+
|
|
151
|
+
Returns
|
|
152
|
+
-------
|
|
153
|
+
dict
|
|
154
|
+
Response shaped exactly as the provider's SDK would return it,
|
|
155
|
+
so callers can switch to TokenPak without code changes.
|
|
156
|
+
|
|
157
|
+
Raises
|
|
158
|
+
------
|
|
159
|
+
TokenPakAdapterError
|
|
160
|
+
If the response is malformed or contains a provider error.
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
@abstractmethod
|
|
164
|
+
def extract_tokens(self, response: dict[str, Any]) -> dict[str, Any]:
|
|
165
|
+
"""Extract token usage counts from a response.
|
|
166
|
+
|
|
167
|
+
Parameters
|
|
168
|
+
----------
|
|
169
|
+
response:
|
|
170
|
+
Either the raw proxy dict or the parsed SDK-format dict.
|
|
171
|
+
|
|
172
|
+
Returns
|
|
173
|
+
-------
|
|
174
|
+
dict with keys:
|
|
175
|
+
- ``input_tokens`` (int) — billed input tokens
|
|
176
|
+
- ``output_tokens`` (int) — billed output tokens
|
|
177
|
+
- ``cache_read`` (int) — tokens served from cache (0 if N/A)
|
|
178
|
+
- ``cache_write`` (int) — tokens written to cache (0 if N/A)
|
|
179
|
+
- ``total`` (int) — ``input_tokens + output_tokens``
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
# ── Convenience helpers ────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
def call(self, request: dict[str, Any]) -> dict[str, Any]:
|
|
185
|
+
"""Full pipeline: prepare → send → parse_response.
|
|
186
|
+
|
|
187
|
+
Returns the provider-native response dict. Logs round-trip time
|
|
188
|
+
at INFO level.
|
|
189
|
+
|
|
190
|
+
Parameters
|
|
191
|
+
----------
|
|
192
|
+
request:
|
|
193
|
+
Raw SDK-style request dict.
|
|
194
|
+
"""
|
|
195
|
+
t0 = time.monotonic()
|
|
196
|
+
prepared = self.prepare_request(request)
|
|
197
|
+
raw_response = self.send(prepared)
|
|
198
|
+
parsed = self.parse_response(raw_response)
|
|
199
|
+
elapsed_ms = (time.monotonic() - t0) * 1000
|
|
200
|
+
self.logger.info(
|
|
201
|
+
"call complete provider=%s model=%s elapsed_ms=%.1f",
|
|
202
|
+
self.provider_name,
|
|
203
|
+
request.get("model", "?"),
|
|
204
|
+
elapsed_ms,
|
|
205
|
+
)
|
|
206
|
+
return parsed
|
|
207
|
+
|
|
208
|
+
def __repr__(self) -> str:
|
|
209
|
+
return (
|
|
210
|
+
f"<{type(self).__name__} provider={self.provider_name!r}"
|
|
211
|
+
f" base_url={self.base_url!r}>"
|
|
212
|
+
)
|