tokeymeter 0.31.1__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.
Files changed (150) hide show
  1. tokeymeter/__init__.py +436 -0
  2. tokeymeter/__main__.py +136 -0
  3. tokeymeter/_api.py +410 -0
  4. tokeymeter/_fidelity.py +4 -0
  5. tokeymeter/_manifest.json +158 -0
  6. tokeymeter/admin.py +497 -0
  7. tokeymeter/audit/__init__.py +7 -0
  8. tokeymeter/backends/__init__.py +10 -0
  9. tokeymeter/cache_admin.py +4 -0
  10. tokeymeter/cache_optimize.py +4 -0
  11. tokeymeter/cascade.py +4 -0
  12. tokeymeter/compression.py +4 -0
  13. tokeymeter/compression_eval.py +4 -0
  14. tokeymeter/content/__init__.py +7 -0
  15. tokeymeter/context_passport.py +4 -0
  16. tokeymeter/decision.py +4 -0
  17. tokeymeter/decorator.py +2406 -0
  18. tokeymeter/degraded.py +4 -0
  19. tokeymeter/demo.py +99 -0
  20. tokeymeter/engines/__init__.py +1 -0
  21. tokeymeter/engines/economics/__init__.py +1 -0
  22. tokeymeter/engines/economics/capacity_report.py +366 -0
  23. tokeymeter/engines/economics/chargeback.py +365 -0
  24. tokeymeter/engines/economics/close_packet.py +503 -0
  25. tokeymeter/engines/economics/hybrid.py +430 -0
  26. tokeymeter/engines/economics/keys.py +274 -0
  27. tokeymeter/engines/economics/pricing.py +496 -0
  28. tokeymeter/engines/economics/reconcile.py +112 -0
  29. tokeymeter/engines/economics/reporting.py +338 -0
  30. tokeymeter/engines/economics/savings.py +779 -0
  31. tokeymeter/engines/economics/usage.py +132 -0
  32. tokeymeter/engines/execution/__init__.py +1 -0
  33. tokeymeter/engines/execution/endpoint.py +92 -0
  34. tokeymeter/engines/execution/halts.py +200 -0
  35. tokeymeter/engines/execution/integrations/README.md +95 -0
  36. tokeymeter/engines/execution/integrations/__init__.py +0 -0
  37. tokeymeter/engines/execution/integrations/anthropic.py +203 -0
  38. tokeymeter/engines/execution/integrations/openai.py +647 -0
  39. tokeymeter/engines/execution/integrations/openai_async.py +421 -0
  40. tokeymeter/engines/execution/integrations/universal.py +177 -0
  41. tokeymeter/engines/execution/response_text.py +133 -0
  42. tokeymeter/engines/execution/task.py +936 -0
  43. tokeymeter/engines/governance/__init__.py +1 -0
  44. tokeymeter/engines/governance/agents.py +557 -0
  45. tokeymeter/engines/governance/agents_html.py +642 -0
  46. tokeymeter/engines/governance/compliance.py +714 -0
  47. tokeymeter/engines/governance/content/REDTEAM_REPORT.md +68 -0
  48. tokeymeter/engines/governance/content/__init__.py +27 -0
  49. tokeymeter/engines/governance/content/secrets.py +796 -0
  50. tokeymeter/engines/governance/decision.py +208 -0
  51. tokeymeter/engines/governance/identity.py +69 -0
  52. tokeymeter/engines/governance/plan.py +489 -0
  53. tokeymeter/engines/governance/policy.py +84 -0
  54. tokeymeter/engines/governance/privacy.py +233 -0
  55. tokeymeter/engines/governance/rules.py +609 -0
  56. tokeymeter/engines/governance/simulate.py +391 -0
  57. tokeymeter/engines/governance/suggest.py +467 -0
  58. tokeymeter/engines/knowledge/__init__.py +1 -0
  59. tokeymeter/engines/knowledge/context_passport.py +93 -0
  60. tokeymeter/engines/knowledge/memory.py +1063 -0
  61. tokeymeter/engines/optimization/__init__.py +1 -0
  62. tokeymeter/engines/optimization/cache_admin.py +19 -0
  63. tokeymeter/engines/optimization/cache_optimize.py +346 -0
  64. tokeymeter/engines/optimization/cascade.py +338 -0
  65. tokeymeter/engines/optimization/compression.py +556 -0
  66. tokeymeter/engines/optimization/compression_eval.py +358 -0
  67. tokeymeter/engines/optimization/envelope.py +115 -0
  68. tokeymeter/engines/optimization/prompt_ir.py +297 -0
  69. tokeymeter/engines/optimization/query_compress.py +421 -0
  70. tokeymeter/engines/optimization/redis_store.py +400 -0
  71. tokeymeter/engines/optimization/router.py +380 -0
  72. tokeymeter/engines/optimization/safe_compress.py +187 -0
  73. tokeymeter/engines/optimization/salience.py +305 -0
  74. tokeymeter/engines/optimization/semantic.py +699 -0
  75. tokeymeter/engines/optimization/semantic_eval.py +404 -0
  76. tokeymeter/engines/optimization/semantic_verify.py +284 -0
  77. tokeymeter/engines/optimization/storage.py +286 -0
  78. tokeymeter/engines/optimization/warmup.py +271 -0
  79. tokeymeter/engines/reliability/__init__.py +1 -0
  80. tokeymeter/engines/reliability/_fidelity.py +192 -0
  81. tokeymeter/engines/reliability/degraded.py +125 -0
  82. tokeymeter/engines/reliability/overhead.py +51 -0
  83. tokeymeter/engines/trust/__init__.py +1 -0
  84. tokeymeter/engines/trust/audit/__init__.py +90 -0
  85. tokeymeter/engines/trust/audit/log.py +921 -0
  86. tokeymeter/engines/trust/audit/proof.py +391 -0
  87. tokeymeter/engines/trust/audit/signers.py +193 -0
  88. tokeymeter/engines/trust/cipher.py +105 -0
  89. tokeymeter/engines/trust/integrity.py +634 -0
  90. tokeymeter/envelope.py +4 -0
  91. tokeymeter/events.py +156 -0
  92. tokeymeter/firstrun.py +215 -0
  93. tokeymeter/identity.py +4 -0
  94. tokeymeter/integrations/__init__.py +9 -0
  95. tokeymeter/integrity.py +4 -0
  96. tokeymeter/keys.py +4 -0
  97. tokeymeter/memory.py +4 -0
  98. tokeymeter/metrics.py +216 -0
  99. tokeymeter/overhead.py +4 -0
  100. tokeymeter/paths.py +79 -0
  101. tokeymeter/policy.py +4 -0
  102. tokeymeter/pricing.py +4 -0
  103. tokeymeter/privacy.py +4 -0
  104. tokeymeter/prompt_ir.py +4 -0
  105. tokeymeter/py.typed +0 -0
  106. tokeymeter/query_compress.py +4 -0
  107. tokeymeter/reporting.py +4 -0
  108. tokeymeter/router.py +4 -0
  109. tokeymeter/runtime/__init__.py +108 -0
  110. tokeymeter/runtime/adapters.py +198 -0
  111. tokeymeter/runtime/bus.py +83 -0
  112. tokeymeter/runtime/catalog.py +157 -0
  113. tokeymeter/runtime/config.py +92 -0
  114. tokeymeter/runtime/conformance.py +167 -0
  115. tokeymeter/runtime/container.py +71 -0
  116. tokeymeter/runtime/economics.py +134 -0
  117. tokeymeter/runtime/enforcement.py +290 -0
  118. tokeymeter/runtime/engine.py +75 -0
  119. tokeymeter/runtime/engines.py +286 -0
  120. tokeymeter/runtime/errors.py +129 -0
  121. tokeymeter/runtime/facade.py +478 -0
  122. tokeymeter/runtime/frameworks.py +91 -0
  123. tokeymeter/runtime/hotreload.py +88 -0
  124. tokeymeter/runtime/kernel.py +239 -0
  125. tokeymeter/runtime/oidc.py +165 -0
  126. tokeymeter/runtime/optimization.py +111 -0
  127. tokeymeter/runtime/optimize.py +225 -0
  128. tokeymeter/runtime/plugins.py +229 -0
  129. tokeymeter/runtime/proof.py +374 -0
  130. tokeymeter/runtime/providers.py +167 -0
  131. tokeymeter/runtime/resilience.py +344 -0
  132. tokeymeter/runtime/telemetry.py +155 -0
  133. tokeymeter/runtime/tools.py +178 -0
  134. tokeymeter/safe_compress.py +4 -0
  135. tokeymeter/salience.py +4 -0
  136. tokeymeter/savings.py +4 -0
  137. tokeymeter/semantic.py +4 -0
  138. tokeymeter/semantic_eval.py +4 -0
  139. tokeymeter/semantic_verify.py +4 -0
  140. tokeymeter/storage.py +4 -0
  141. tokeymeter/usage.py +4 -0
  142. tokeymeter/utils.py +42 -0
  143. tokeymeter/warmup.py +4 -0
  144. tokeymeter/watch.py +438 -0
  145. tokeymeter-0.31.1.dist-info/METADATA +982 -0
  146. tokeymeter-0.31.1.dist-info/RECORD +150 -0
  147. tokeymeter-0.31.1.dist-info/WHEEL +5 -0
  148. tokeymeter-0.31.1.dist-info/entry_points.txt +2 -0
  149. tokeymeter-0.31.1.dist-info/licenses/LICENSE +178 -0
  150. tokeymeter-0.31.1.dist-info/top_level.txt +1 -0
tokeymeter/__init__.py ADDED
@@ -0,0 +1,436 @@
1
+ """
2
+ Tokeymeter — industrial-grade LLM cost optimization.
3
+
4
+ Two-tier cache (exact + semantic) with TTL, single-flight dedup, optional
5
+ sqlite-vec acceleration, sync + async + streaming. Plus a trust layer:
6
+ observable events, shadow mode, PII redaction, workload tagging.
7
+
8
+ Quick start:
9
+
10
+ import tokeymeter
11
+
12
+ @tokeymeter.cache(
13
+ model="gpt-4o-mini",
14
+ semantic=True, # paraphrase matching
15
+ ttl=3600, # entries expire after 1 hour
16
+ shadow=False, # set True to measure without serving cached
17
+ redactor=tokeymeter.privacy.default_redactor, # strip PII before caching
18
+ tag="support", # workload label for reporting
19
+ )
20
+ async def ask(prompt: str) -> str:
21
+ return await openai_call(prompt)
22
+
23
+ # Wire observability
24
+ @tokeymeter.events.on_event
25
+ def to_datadog(event):
26
+ statsd.increment("tokeymeter.lookup",
27
+ tags=[f"hit_type:{event.hit_type or 'miss'}"])
28
+
29
+ print(tokeymeter.savings_report())
30
+ """
31
+ from . import admin, audit, backends, compression, events, integrity, memory, metrics, privacy, warmup
32
+ from .cache_admin import clear_cache
33
+ from .compression import (
34
+ Compressor,
35
+ CompressionResult,
36
+ LLMLinguaCompressor,
37
+ StructuralCompressor,
38
+ compose,
39
+ safe_compress,
40
+ )
41
+ from .salience import SalienceCompressor
42
+ from .safe_compress import SafeCompressor, compression_stats, reset_compression_stats
43
+ from .router import Router, RouteDecision, Cascade, CascadeResult
44
+ from .cache_optimize import CacheOptimizer, CacheReport, VolatileFinding
45
+ from .decorator import (
46
+ cache,
47
+ cache_stream,
48
+ lineage,
49
+ tenant_scope,
50
+ set_compression_max_reduction,
51
+ set_fidelity_circuit_breaker,
52
+ compression_breaker_state,
53
+ set_event_preview_policy,
54
+ set_runtime_guards,
55
+ enterprise_defaults,
56
+ compression_verification_log,
57
+ memory_fidelity_log,
58
+ set_default_redactor,
59
+ set_default_semantic_cache,
60
+ set_default_store,
61
+ with_memory,
62
+ )
63
+ from .envelope import unwrap, wrap
64
+ from . import prompt_ir # Phase 0 Prompt IR substrate (namespaced; consumes nothing yet)
65
+ from .policy import (
66
+ SecurityPolicy,
67
+ SecurityPolicyError,
68
+ set_security_policy,
69
+ get_security_policy,
70
+ reset_security_policy,
71
+ )
72
+ from .decision import (
73
+ DecisionRecord,
74
+ on_decision,
75
+ remove_decision_subscriber,
76
+ clear_decision_subscribers,
77
+ )
78
+ from .memory import (
79
+ CallableSummarizer,
80
+ ConversationMemory,
81
+ InMemoryMemoryStore,
82
+ SQLiteMemoryStore,
83
+ Summarizer,
84
+ Turn,
85
+ TruncationSummarizer,
86
+ )
87
+ from .metrics import PrometheusCollector
88
+ from .integrity import scan_environment, self_check, verify_self
89
+ from .backends import RedisStore, FernetCipher, NoOpCipher
90
+ from .privacy import DefaultRedactor, default_redactor, redact
91
+ from .savings import (reset_savings, savings_report, savings_ledger_health, set_home,
92
+ set_savings_path, set_buffered_savings, set_in_memory_savings, flush_savings,
93
+ capacity_report)
94
+ from .identity import set_principal, get_principal, principal
95
+ from .engines.governance.rules import (
96
+ RuleSet, Rule, Condition, RulePolicyError,
97
+ load_rules, load_rules_file, set_rules, get_rules, clear_rules,
98
+ rules_version, set_env, current_env)
99
+ from .engines.execution.halts import (
100
+ HaltEvent, on_halt, remove_halt_handler, clear_halt_handlers,
101
+ halt_handler_count)
102
+ from .engines.governance.compliance import (
103
+ data_class, set_data_class, current_data_class,
104
+ region, set_region, current_region,
105
+ PolicyViolation, ModelNotPermitted, EndpointNotPermitted,
106
+ resolve_compliance,
107
+ STARTER_POLICIES, starter_policy, list_starter_policies, policy_report)
108
+ from .engines.governance.simulate import simulate_rules
109
+ from .engines.governance.plan import (
110
+ plan_report, coverage_report, render_plan)
111
+ from .engines.governance.agents import (
112
+ agent_report, render_agents, compare_reports, render_comparison)
113
+ from .engines.governance.suggest import suggest_thresholds, render_suggestions
114
+ from .engines.governance.agents_html import render_agents_html, write_agents_html
115
+ # LangChain / LlamaIndex adapters. Importing this module never requires either
116
+ # package to be installed — the adapters duck-type whatever is handed to them.
117
+ from .runtime.frameworks import wrap_langchain_llm, wrap_llamaindex_llm
118
+ from .engines.execution.task import (
119
+ task, bind_task, set_task, get_task, current_task_id, current_agent,
120
+ task_snapshot,
121
+ TaskLimitExceeded, TaskEnvelopeExceeded, TaskLoopDetected,
122
+ TaskStalled, TaskCallLimitExceeded)
123
+ from .engines.execution.endpoint import set_endpoint, get_endpoint, endpoint
124
+ from .usage import set_queue_wait_ms
125
+ from .keys import (register_key, unregister_key, clear_keys, key,
126
+ key_status, leak_scan, get_current_key, KeyBudgetExceeded)
127
+ from . import context_passport
128
+ from .pricing import (register_pricing, unregister_pricing, clear_registered_pricing,
129
+ registered_pricing, pricing_info, estimate_cost_with_source,
130
+ derive_selfhost_rate, register_selfhost_pricing, capacity_reclaimed,
131
+ register_cluster_costs, derive_cluster_gpu_hour_rate)
132
+ from .engines.economics.capacity_report import capacity_recovery_report
133
+ from .engines.economics.chargeback import chargeback_report, chargeback_csv
134
+ from .engines.economics.hybrid import hybrid_placement_report, hybrid_placement_csv
135
+ from .engines.economics.close_packet import (
136
+ close_packet, general_ledger_rows, general_ledger_csv)
137
+ from .storage import MemoryStore, SQLiteStore
138
+
139
+ # Semantic primitives are imported lazily to avoid hard-failing when
140
+ # extras aren't installed.
141
+ try:
142
+ from .semantic import SemanticCache, is_available as semantic_available
143
+ from .semantic import is_vec_index_available
144
+ except ImportError:
145
+ SemanticCache = None # type: ignore[assignment]
146
+
147
+ def semantic_available() -> bool: # type: ignore[no-redef]
148
+ return False
149
+
150
+ def is_vec_index_available() -> bool: # type: ignore[no-redef]
151
+ return False
152
+
153
+
154
+ __version__ = "0.31.1"
155
+
156
+ # ---------------------------------------------------------------------------
157
+ # THE PRIMARY SURFACE
158
+ #
159
+ # 164 public names is a complete toolbox and a poor first impression: someone
160
+ # opening this package has no way to tell which twenty things ARE the product
161
+ # and which hundred-and-forty are the machinery underneath.
162
+ #
163
+ # Nothing is removed — deleting public API from a shipped package breaks
164
+ # working code and buys nothing. Instead the product is NAMED here, so docs,
165
+ # tooling and a new reader all have one honest entry point. Everything absent
166
+ # from this list still works exactly as before.
167
+ # ---------------------------------------------------------------------------
168
+ PRIMARY_API = (
169
+ # Install it: one decorator on the function that calls a model.
170
+ "cache", "cache_stream",
171
+ # Bound a unit of agent work. An agent task is 10-100 calls; this is the
172
+ # boundary everything else keys on.
173
+ "task", "bind_task", "TaskLimitExceeded",
174
+ # Price what you use, on an API or on your own hardware.
175
+ "register_pricing", "register_cluster_costs",
176
+ # Declare the rules once; the node enforces them everywhere.
177
+ "load_rules_file", "set_rules",
178
+ # See what happened, and what a rule WOULD do before you apply it.
179
+ "agent_report", "plan_report", "savings_report", "report",
180
+ # Money, in the shape finance already works in.
181
+ "chargeback_report", "close_packet", "general_ledger_csv",
182
+ # Owned hardware.
183
+ "capacity_recovery_report", "hybrid_placement_report",
184
+ # Is it healthy, and is it what we shipped?
185
+ "doctor", "verify_self",
186
+ )
187
+
188
+
189
+ def primary_api() -> "list[tuple[str, str]]":
190
+ """The product's main surface, with the first line of each docstring.
191
+
192
+ Written for a person opening this package for the first time and for the
193
+ docs build — not as a substitute for `__all__`, which remains the complete
194
+ public API.
195
+ """
196
+ out = []
197
+ for name in PRIMARY_API:
198
+ obj = globals().get(name)
199
+ doc = (getattr(obj, "__doc__", "") or "").strip().split("\n")[0]
200
+ out.append((name, doc))
201
+ return out
202
+
203
+
204
+ __all__ = [
205
+ "PRIMARY_API",
206
+ "primary_api",
207
+ # Core
208
+ "cache",
209
+ "no_optimize",
210
+ "lineage",
211
+ "tenant_scope",
212
+ "set_compression_max_reduction",
213
+ "set_fidelity_circuit_breaker",
214
+ "compression_breaker_state",
215
+ "set_event_preview_policy",
216
+ "set_runtime_guards",
217
+ "enterprise_defaults",
218
+ "cache_stream",
219
+ "savings_report",
220
+ "capacity_report",
221
+ # Identity binding (v0.14 keystone)
222
+ "set_principal",
223
+ "get_principal",
224
+ "principal",
225
+ "set_endpoint",
226
+ "get_endpoint",
227
+ "endpoint",
228
+ "task",
229
+ "bind_task",
230
+ "RuleSet",
231
+ "Rule",
232
+ "Condition",
233
+ "RulePolicyError",
234
+ "load_rules",
235
+ "load_rules_file",
236
+ "set_rules",
237
+ "get_rules",
238
+ "clear_rules",
239
+ "rules_version",
240
+ "set_env",
241
+ "current_env",
242
+ "data_class",
243
+ "set_data_class",
244
+ "current_data_class",
245
+ "region",
246
+ "set_region",
247
+ "current_region",
248
+ "PolicyViolation",
249
+ "ModelNotPermitted",
250
+ "EndpointNotPermitted",
251
+ "resolve_compliance",
252
+ "STARTER_POLICIES",
253
+ "starter_policy",
254
+ "list_starter_policies",
255
+ "policy_report",
256
+ "HaltEvent",
257
+ "on_halt",
258
+ "remove_halt_handler",
259
+ "clear_halt_handlers",
260
+ "halt_handler_count",
261
+ "simulate_rules",
262
+ "plan_report",
263
+ "coverage_report",
264
+ "render_plan",
265
+ "agent_report",
266
+ "render_agents",
267
+ "compare_reports",
268
+ "render_comparison",
269
+ "suggest_thresholds",
270
+ "render_suggestions",
271
+ "render_agents_html",
272
+ "write_agents_html",
273
+ "wrap_langchain_llm",
274
+ "wrap_llamaindex_llm",
275
+ "set_task",
276
+ "get_task",
277
+ "current_task_id",
278
+ "current_agent",
279
+ "task_snapshot",
280
+ "TaskLimitExceeded",
281
+ "TaskEnvelopeExceeded",
282
+ "TaskLoopDetected",
283
+ "TaskStalled",
284
+ "TaskCallLimitExceeded",
285
+ "set_queue_wait_ms",
286
+ # Budget-Enforced Keys (T3.3)
287
+ "register_key",
288
+ "unregister_key",
289
+ "clear_keys",
290
+ "key",
291
+ "key_status",
292
+ "leak_scan",
293
+ "get_current_key",
294
+ "KeyBudgetExceeded",
295
+ "context_passport",
296
+ "savings_ledger_health",
297
+ # Pricing registry (v0.13) — anti-fabrication layer
298
+ "register_pricing",
299
+ "unregister_pricing",
300
+ "clear_registered_pricing",
301
+ "registered_pricing",
302
+ "pricing_info",
303
+ "estimate_cost_with_source",
304
+ "derive_selfhost_rate",
305
+ "register_selfhost_pricing",
306
+ "register_cluster_costs",
307
+ "derive_cluster_gpu_hour_rate",
308
+ "capacity_recovery_report",
309
+ "chargeback_report",
310
+ "chargeback_csv",
311
+ "hybrid_placement_report",
312
+ "hybrid_placement_csv",
313
+ "close_packet",
314
+ "general_ledger_rows",
315
+ "general_ledger_csv",
316
+ "capacity_reclaimed",
317
+ "set_home",
318
+ "set_savings_path",
319
+ "set_buffered_savings",
320
+ "set_in_memory_savings",
321
+ "flush_savings",
322
+ "doctor",
323
+ "reset_savings",
324
+ "clear_cache",
325
+ # Configuration
326
+ "set_default_store",
327
+ "set_default_semantic_cache",
328
+ "set_default_redactor",
329
+ # Storage
330
+ "MemoryStore",
331
+ "SQLiteStore",
332
+ "SemanticCache",
333
+ "semantic_available",
334
+ "is_vec_index_available",
335
+ # Trust + ops layer
336
+ "events",
337
+ "privacy",
338
+ "admin",
339
+ "metrics",
340
+ "warmup",
341
+ "compression",
342
+ "memory",
343
+ "audit",
344
+ "integrity",
345
+ "verify_self",
346
+ "scan_environment",
347
+ "self_check",
348
+ "backends",
349
+ "RedisStore",
350
+ "FernetCipher",
351
+ "NoOpCipher",
352
+ "DefaultRedactor",
353
+ "default_redactor",
354
+ "redact",
355
+ "PrometheusCollector",
356
+ # Compression (v0.6)
357
+ "Compressor",
358
+ "CompressionResult",
359
+ "StructuralCompressor",
360
+ "LLMLinguaCompressor",
361
+ "SalienceCompressor",
362
+ "SafeCompressor",
363
+ "compression_stats",
364
+ "reset_compression_stats",
365
+ "Router",
366
+ "RouteDecision",
367
+ "Cascade",
368
+ "CascadeResult",
369
+ "CacheOptimizer",
370
+ "CacheReport",
371
+ "VolatileFinding",
372
+ "compress_tools",
373
+ "compose",
374
+ "safe_compress",
375
+ "compression_verification_log",
376
+ # Memory (v0.7)
377
+ "with_memory",
378
+ "ConversationMemory",
379
+ "Turn",
380
+ "Summarizer",
381
+ "TruncationSummarizer",
382
+ "CallableSummarizer",
383
+ "InMemoryMemoryStore",
384
+ "SQLiteMemoryStore",
385
+ "memory_fidelity_log",
386
+ # Envelope (advanced)
387
+ "wrap",
388
+ "unwrap",
389
+ "prompt_ir",
390
+ "SecurityPolicy",
391
+ "SecurityPolicyError",
392
+ "set_security_policy",
393
+ "get_security_policy",
394
+ "reset_security_policy",
395
+ "DecisionRecord",
396
+ "on_decision",
397
+ "remove_decision_subscriber",
398
+ "clear_decision_subscribers",
399
+ ]
400
+
401
+ # ---------------------------------------------------------------------------
402
+ # Tokeymeter public product surface (the curated facade). Re-exported here so
403
+ # `from tokeymeter import meter, report, ...` works. Implemented in _api.py to
404
+ # keep this module focused on the engine's symbol table. The facade's
405
+ # compress_tools/no_optimize intentionally rebind the engine names above to the
406
+ # product-facing wrappers.
407
+ # ---------------------------------------------------------------------------
408
+ from ._api import ( # noqa: E402
409
+ meter,
410
+ report,
411
+ reset,
412
+ doctor,
413
+ compressor,
414
+ router,
415
+ metered_route,
416
+ cascade,
417
+ optimize_cache,
418
+ check_cache,
419
+ compress_tools,
420
+ no_optimize,
421
+ )
422
+
423
+ __all__ += [
424
+ "meter",
425
+ "report",
426
+ "reset",
427
+ "compressor",
428
+ "router",
429
+ "metered_route",
430
+ "cascade",
431
+ "optimize_cache",
432
+ "check_cache",
433
+ ]
434
+
435
+ # K4 (W3): the one-line Runtime facade — `Runtime(client=...).execute(prompt)`.
436
+ from .runtime.facade import Runtime, RuntimeConfigurationError # noqa: E402
tokeymeter/__main__.py ADDED
@@ -0,0 +1,136 @@
1
+ """
2
+ Tokeymeter command-line entry point.
3
+
4
+ tokeymeter firstrun # 60 seconds: watch a stuck agent stop
5
+ tokeymeter watch # see tasks as they run, live
6
+ tokeymeter agents # how your agents behave: cost per task,
7
+ # progress, and which ones stalled
8
+ tokeymeter plan --policy FILE # what an execution policy WOULD do,
9
+ # replayed against your own history
10
+ tokeymeter doctor # verdict + overhead p50/p95/p99 + ledger health
11
+ tokeymeter report --demo # savings report (no keys)
12
+ tokeymeter report --provider openai # against your real OPENAI_API_KEY
13
+ tokeymeter demo # self-hosted walkthrough (offline, no keys)
14
+ tokeymeter pricing # list-table age + registered overrides
15
+ tokeymeter version
16
+
17
+ `python -m tokeymeter ...` and the installed `tokeymeter` script both land here.
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import sys
22
+
23
+
24
+ def _make_console_resilient() -> None:
25
+ """Ensure CLI output can never crash on a legacy console code page.
26
+
27
+ Windows consoles still default to cp1252 in many environments, and a single
28
+ non-ASCII character in an otherwise fine report (an arrow, a middle dot)
29
+ raises UnicodeEncodeError and takes the whole command down. `tokeymeter
30
+ demo` did exactly that on '\\u2192'.
31
+
32
+ We keep the stream's own encoding — rewriting it to UTF-8 would emit bytes a
33
+ cp1252 console renders as mojibake — and only switch the error handler to
34
+ 'replace'. On a UTF-8 terminal output is unchanged; on a legacy one an
35
+ unrepresentable glyph degrades to '?' instead of aborting the command.
36
+ A diagnostic tool that dies while printing diagnostics is worse than one
37
+ that prints an imperfect character."""
38
+ for stream in (sys.stdout, sys.stderr):
39
+ reconfigure = getattr(stream, "reconfigure", None)
40
+ if reconfigure is None: # not a TextIOWrapper (piped/captured)
41
+ continue
42
+ try:
43
+ reconfigure(errors="replace")
44
+ except Exception:
45
+ # Console setup must never be the reason a command fails.
46
+ pass
47
+
48
+
49
+ def _print_doctor() -> None:
50
+ """Render tokeymeter.doctor() — one-line verdict first, then cache-hit
51
+ overhead percentiles, savings mode, ledger health, degraded counts."""
52
+ import json
53
+ import tokeymeter
54
+ d = tokeymeter.doctor()
55
+ oh = d["cache_hit_overhead"]
56
+ h = d["ledger_health"]
57
+
58
+ def ms(v):
59
+ return "n/a" if v is None else f"{v}ms"
60
+
61
+ degraded = bool(h.get("degraded")) or (d.get("degraded_events", 0) or 0) > 0
62
+ if degraded:
63
+ why = []
64
+ if h.get("degraded"):
65
+ why.append("ledger degraded")
66
+ if (d.get("degraded_events", 0) or 0) > 0:
67
+ why.append(f"{d['degraded_events']} degraded event(s)")
68
+ verdict = "DEGRADED — " + ", ".join(why)
69
+ else:
70
+ verdict = "HEALTHY"
71
+
72
+ print(f"tokeymeter doctor | {verdict}")
73
+ print(" cache-hit overhead: "
74
+ f"p50={ms(oh['p50_ms'])} p95={ms(oh['p95_ms'])} "
75
+ f"p99={ms(oh['p99_ms'])} (n={oh['samples']})")
76
+ print(f" savings mode: {d['savings_mode']}")
77
+ print(f" ledger: writable={h['writable']} degraded={h['degraded']} "
78
+ f"records={h['records_written']} write_failures={h['write_failures']}")
79
+ print(f" degraded events: {d['degraded_events']} by_source={d['degraded_by_source']}")
80
+ # machine-readable line for scripting (kept stable)
81
+ print(json.dumps(d))
82
+
83
+
84
+ def main(argv=None) -> None:
85
+ _make_console_resilient()
86
+ argv = list(sys.argv[1:] if argv is None else argv)
87
+ if not argv or argv[0] in ("-h", "--help", "help"):
88
+ print(__doc__.strip())
89
+ return
90
+ cmd, rest = argv[0], argv[1:]
91
+ if cmd == "report":
92
+ from tokeymeter import reporting
93
+ sys.argv = ["tokeymeter report", *rest]
94
+ reporting.main()
95
+ elif cmd == "doctor":
96
+ _print_doctor()
97
+ elif cmd == "pricing":
98
+ from tokeymeter import pricing as _pr
99
+ age = _pr.pricing_age_days()
100
+ stale = age > 45
101
+ print(f"tokeymeter pricing | list table as of {_pr.PRICING_AS_OF} "
102
+ f"({age}d old){' | STALE - refresh recommended' if stale else ''}")
103
+ reg = _pr.registered_pricing()
104
+ if reg:
105
+ print(f" registered overrides ({len(reg)}):")
106
+ for m, e in sorted(reg.items()):
107
+ print(f" {m:<28} in=${e['input']:.4f}/1M out=${e['output']:.4f}/1M")
108
+ else:
109
+ print(" registered overrides: none")
110
+ print(" note: registered rates always take precedence over the list table.")
111
+ elif cmd == "firstrun":
112
+ from tokeymeter import firstrun as _firstrun
113
+ sys.exit(_firstrun.main(rest))
114
+ elif cmd == "watch":
115
+ from tokeymeter import watch as _watch
116
+ sys.exit(_watch.main(rest))
117
+ elif cmd == "agents":
118
+ from tokeymeter.engines.governance import agents as _agents
119
+ sys.exit(_agents.main(rest))
120
+ elif cmd == "plan":
121
+ from tokeymeter.engines.governance import plan as _plan
122
+ sys.exit(_plan.main(rest))
123
+ elif cmd == "demo":
124
+ from tokeymeter import demo
125
+ sys.exit(demo.run())
126
+ elif cmd in ("version", "--version", "-V"):
127
+ import tokeymeter
128
+ print("tokeymeter", getattr(tokeymeter, "__version__", "unknown"))
129
+ else:
130
+ print(f"unknown command: {cmd}\n")
131
+ print(__doc__.strip())
132
+ sys.exit(2)
133
+
134
+
135
+ if __name__ == "__main__":
136
+ main()