virtual-context 0.2.5__tar.gz → 0.2.7__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.
Files changed (221) hide show
  1. {virtual_context-0.2.5 → virtual_context-0.2.7}/PKG-INFO +5 -2
  2. {virtual_context-0.2.5 → virtual_context-0.2.7}/README.md +4 -1
  3. virtual_context-0.2.7/assets/dashboard.png +0 -0
  4. {virtual_context-0.2.5 → virtual_context-0.2.7}/pyproject.toml +1 -1
  5. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_budget_enforcement.py +208 -1
  6. virtual_context-0.2.7/tests/test_format_agnostic.py +614 -0
  7. virtual_context-0.2.7/tests/test_turn_grouping.py +150 -0
  8. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/__init__.py +1 -1
  9. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/conversation_identity.py +22 -6
  10. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/formats.py +632 -0
  11. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/media.py +81 -67
  12. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/message_filter.py +831 -701
  13. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/server.py +23 -5
  14. {virtual_context-0.2.5 → virtual_context-0.2.7}/.gitignore +0 -0
  15. {virtual_context-0.2.5 → virtual_context-0.2.7}/LICENSE +0 -0
  16. {virtual_context-0.2.5 → virtual_context-0.2.7}/assets/hero.png +0 -0
  17. {virtual_context-0.2.5 → virtual_context-0.2.7}/models.yaml +0 -0
  18. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/REGRESSION_MAP.md +0 -0
  19. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/conftest.py +0 -0
  20. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/docker-compose.test.yml +0 -0
  21. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/haiku/__init__.py +0 -0
  22. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/haiku/conftest.py +0 -0
  23. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/haiku/test_compaction.py +0 -0
  24. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/haiku/test_retrieval.py +0 -0
  25. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/haiku/test_tagging.py +0 -0
  26. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/ollama/__init__.py +0 -0
  27. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/ollama/conftest.py +0 -0
  28. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/ollama/test_compactor.py +0 -0
  29. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/ollama/test_pipeline.py +0 -0
  30. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/ollama/test_provider.py +0 -0
  31. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/ollama/test_tag_generator.py +0 -0
  32. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/proxy/__init__.py +0 -0
  33. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/proxy/test_dashboard_cors.py +0 -0
  34. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/proxy/test_metrics.py +0 -0
  35. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_assembler.py +0 -0
  36. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_backend_integration.py +0 -0
  37. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_cli_init.py +0 -0
  38. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_compaction_commit_prune.py +0 -0
  39. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_compactor.py +0 -0
  40. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_compactor_concurrent.py +0 -0
  41. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_composite_store.py +0 -0
  42. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_config.py +0 -0
  43. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_context_bleed.py +0 -0
  44. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_conversation_identity.py +0 -0
  45. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_conversation_lifecycle.py +0 -0
  46. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_conversation_scoping.py +0 -0
  47. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_embedding_tag_generator.py +0 -0
  48. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_empty_turn_skip.py +0 -0
  49. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_engine_integration.py +0 -0
  50. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_engine_lookback.py +0 -0
  51. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_engine_state.py +0 -0
  52. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_fact_enrichment.py +0 -0
  53. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_fact_graph_integration.py +0 -0
  54. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_fact_link_checker.py +0 -0
  55. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_fact_link_query.py +0 -0
  56. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_fact_link_types.py +0 -0
  57. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_fact_links_sqlite.py +0 -0
  58. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_find_quote.py +0 -0
  59. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_headless.py +0 -0
  60. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_history_filter.py +0 -0
  61. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_idf_retrieval.py +0 -0
  62. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_ingest_index_integrity.py +0 -0
  63. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_longmemeval_auth.py +0 -0
  64. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_mcp_server.py +0 -0
  65. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_media.py +0 -0
  66. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_message_filter.py +0 -0
  67. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_metrics_persistence.py +0 -0
  68. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_model_catalog.py +0 -0
  69. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_model_limits.py +0 -0
  70. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_monitor.py +0 -0
  71. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_multi_instance.py +0 -0
  72. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_noop_fact_link_store.py +0 -0
  73. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_openrouter_provider.py +0 -0
  74. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_paging.py +0 -0
  75. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_passthrough_filter.py +0 -0
  76. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_presets.py +0 -0
  77. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_prev_context_leak.py +0 -0
  78. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_provider_adapters.py +0 -0
  79. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_proxy.py +0 -0
  80. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_proxy_dashboard.py +0 -0
  81. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_proxy_formats.py +0 -0
  82. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_proxy_message_filter.py +0 -0
  83. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_proxy_session.py +0 -0
  84. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_proxy_streaming.py +0 -0
  85. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_raw_content.py +0 -0
  86. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_recall_all.py +0 -0
  87. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_registry_lifecycle.py +0 -0
  88. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_request_captures_persistence.py +0 -0
  89. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_retriever.py +0 -0
  90. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_rrf_scoring.py +0 -0
  91. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_segmenter.py +0 -0
  92. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_semantic_search.py +0 -0
  93. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_sender_identity.py +0 -0
  94. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_session_cache.py +0 -0
  95. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_session_date.py +0 -0
  96. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_storage_protocols.py +0 -0
  97. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_store_sqlite.py +0 -0
  98. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_stub_turn_handling.py +0 -0
  99. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_supersession.py +0 -0
  100. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_supersession_migration.py +0 -0
  101. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_tag_canonicalizer.py +0 -0
  102. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_tag_consolidator.py +0 -0
  103. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_tag_generator.py +0 -0
  104. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_tag_splitter.py +0 -0
  105. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_telemetry.py +0 -0
  106. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_telemetry_integration.py +0 -0
  107. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_tool_loop.py +0 -0
  108. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_tool_output_interceptor.py +0 -0
  109. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_tool_result_filter.py +0 -0
  110. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_tool_tags.py +0 -0
  111. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_tui.py +0 -0
  112. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_turn_tag_index.py +0 -0
  113. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_unified_budget.py +0 -0
  114. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_upstream_trim.py +0 -0
  115. {virtual_context-0.2.5 → virtual_context-0.2.7}/tests/test_verb_expansion.py +0 -0
  116. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual-context.yaml.example +0 -0
  117. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/cli/__init__.py +0 -0
  118. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/cli/main.py +0 -0
  119. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/config.py +0 -0
  120. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/__init__.py +0 -0
  121. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/assembler.py +0 -0
  122. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/compaction_pipeline.py +0 -0
  123. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/compactor.py +0 -0
  124. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/composite_store.py +0 -0
  125. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/conversation_store.py +0 -0
  126. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/embedding_provider.py +0 -0
  127. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/embedding_tag_generator.py +0 -0
  128. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/engine_utils.py +0 -0
  129. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/fact_query.py +0 -0
  130. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/fts_preprocessor.py +0 -0
  131. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/hint_builder.py +0 -0
  132. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/llm_utils.py +0 -0
  133. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/math_utils.py +0 -0
  134. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/model_catalog.py +0 -0
  135. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/monitor.py +0 -0
  136. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/paging_manager.py +0 -0
  137. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/protocols.py +0 -0
  138. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/provider_adapters.py +0 -0
  139. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/quote_search.py +0 -0
  140. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/retrieval_assembler.py +0 -0
  141. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/retrieval_scoring.py +0 -0
  142. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/retriever.py +0 -0
  143. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/search_engine.py +0 -0
  144. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/segmenter.py +0 -0
  145. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/semantic_search.py +0 -0
  146. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/store.py +0 -0
  147. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/tag_canonicalizer.py +0 -0
  148. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/tag_consolidator.py +0 -0
  149. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/tag_generator.py +0 -0
  150. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/tag_scoring.py +0 -0
  151. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/tag_splitter.py +0 -0
  152. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/tagging_pipeline.py +0 -0
  153. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/telemetry.py +0 -0
  154. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/temporal_resolver.py +0 -0
  155. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/tool_loop.py +0 -0
  156. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/tool_query.py +0 -0
  157. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/core/turn_tag_index.py +0 -0
  158. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/data/anthropic-tokenizer/tokenizer.json +0 -0
  159. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/engine.py +0 -0
  160. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/ingest/__init__.py +0 -0
  161. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/ingest/curator.py +0 -0
  162. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/ingest/date_resolver.py +0 -0
  163. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/ingest/parsers.py +0 -0
  164. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/ingest/supersession.py +0 -0
  165. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/mcp/__init__.py +0 -0
  166. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/mcp/server.py +0 -0
  167. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/model_limits.py +0 -0
  168. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/openclaw/virtual-context.mjs +0 -0
  169. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/patterns.py +0 -0
  170. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/presets/__init__.py +0 -0
  171. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/presets/agentic.py +0 -0
  172. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/presets/base.py +0 -0
  173. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/presets/coding.py +0 -0
  174. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/providers/__init__.py +0 -0
  175. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/providers/anthropic.py +0 -0
  176. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/providers/base.py +0 -0
  177. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/providers/generic_openai.py +0 -0
  178. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/providers/ollama_native.py +0 -0
  179. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/__init__.py +0 -0
  180. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/_envelope.py +0 -0
  181. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/dashboard.html +0 -0
  182. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/dashboard.py +0 -0
  183. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/handlers.py +0 -0
  184. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/helpers.py +0 -0
  185. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/metrics.py +0 -0
  186. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/multi.py +0 -0
  187. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/registry.py +0 -0
  188. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/session_cache.py +0 -0
  189. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/state.py +0 -0
  190. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/static/android-chrome-192x192.png +0 -0
  191. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/static/android-chrome-512x512.png +0 -0
  192. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/static/apple-touch-icon.png +0 -0
  193. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/static/favicon-16x16.png +0 -0
  194. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/static/favicon-32x32.png +0 -0
  195. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/static/favicon.ico +0 -0
  196. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/static/site.webmanifest +0 -0
  197. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/proxy/tool_output_interceptor.py +0 -0
  198. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/storage/__init__.py +0 -0
  199. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/storage/falkordb.py +0 -0
  200. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/storage/filesystem.py +0 -0
  201. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/storage/helpers.py +0 -0
  202. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/storage/neo4j.py +0 -0
  203. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/storage/noop_fact_link_store.py +0 -0
  204. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/storage/postgres.py +0 -0
  205. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/storage/sqlite.py +0 -0
  206. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/token_counter.py +0 -0
  207. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/tui/__init__.py +0 -0
  208. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/tui/app.py +0 -0
  209. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/tui/chat.tcss +0 -0
  210. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/tui/chat_provider.py +0 -0
  211. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/tui/headless.py +0 -0
  212. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/tui/modals/__init__.py +0 -0
  213. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/tui/modals/turn_inspector.py +0 -0
  214. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/tui/state.py +0 -0
  215. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/tui/widgets/__init__.py +0 -0
  216. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/tui/widgets/budget_bar.py +0 -0
  217. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/tui/widgets/chat_view.py +0 -0
  218. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/tui/widgets/input_box.py +0 -0
  219. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/tui/widgets/tag_panel.py +0 -0
  220. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/tui/widgets/turn_list.py +0 -0
  221. {virtual_context-0.2.5 → virtual_context-0.2.7}/virtual_context/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: virtual-context
3
- Version: 0.2.5
3
+ Version: 0.2.7
4
4
  Summary: OS-style virtual memory for LLM session context management
5
5
  Project-URL: Homepage, https://virtual-context.com
6
6
  Project-URL: Repository, https://github.com/virtual-context/virtual-context
@@ -46,8 +46,11 @@ Requires-Dist: redis>=5.0; extra == 'redis'
46
46
  Description-Content-Type: text/markdown
47
47
 
48
48
  <p align="center">
49
- <img src="assets/hero.png" alt="virtual-context" width="100%">
49
+ <a href="assets/dashboard.png">
50
+ <img src="assets/dashboard.png" alt="virtual-context dashboard" width="800">
51
+ </a>
50
52
  </p>
53
+ <p align="center"><sub>virtual-context cloud - running 3 million virtual token window at 80k actual tokens</sub></p>
51
54
 
52
55
  # virtual-context
53
56
 
@@ -1,6 +1,9 @@
1
1
  <p align="center">
2
- <img src="assets/hero.png" alt="virtual-context" width="100%">
2
+ <a href="assets/dashboard.png">
3
+ <img src="assets/dashboard.png" alt="virtual-context dashboard" width="800">
4
+ </a>
3
5
  </p>
6
+ <p align="center"><sub>virtual-context cloud - running 3 million virtual token window at 80k actual tokens</sub></p>
4
7
 
5
8
  # virtual-context
6
9
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "virtual-context"
7
- version = "0.2.5"
7
+ version = "0.2.7"
8
8
  description = "OS-style virtual memory for LLM session context management"
9
9
  readme = "README.md"
10
10
  license = "AGPL-3.0-or-later"
@@ -34,7 +34,10 @@ class TestScanReducibleItems:
34
34
  assert thinking_items[0].size_bytes > 0
35
35
 
36
36
  def test_finds_tool_results(self):
37
+ # Need 3+ turn groups so the tool result falls outside the last-2 window.
37
38
  msgs = [
39
+ {"role": "user", "content": "old question"},
40
+ {"role": "assistant", "content": "old answer"},
38
41
  {"role": "user", "content": "do something"},
39
42
  {"role": "assistant", "content": [
40
43
  {"type": "tool_use", "id": "t1", "name": "Read", "input": {}},
@@ -44,13 +47,15 @@ class TestScanReducibleItems:
44
47
  ]},
45
48
  {"role": "assistant", "content": [{"type": "text", "text": "done"}]},
46
49
  {"role": "user", "content": "thanks"},
50
+ {"role": "assistant", "content": "welcome"},
51
+ {"role": "user", "content": "current"},
47
52
  ]
48
53
  body = _make_payload(msgs)
49
54
  fmt = detect_format(body)
50
55
  items = scan_reducible_items(body, fmt)
51
56
  tr_items = [i for i in items if i.category == "tool_result"]
52
57
  assert len(tr_items) == 1
53
- assert tr_items[0].msg_index == 2
58
+ assert tr_items[0].msg_index == 4
54
59
 
55
60
  def test_last_2_turns_tool_results_categorized_separately(self):
56
61
  msgs = [
@@ -151,6 +156,50 @@ class TestScanReducibleItems:
151
156
  items = scan_reducible_items(body, fmt)
152
157
  assert not any(i.category == "image" for i in items)
153
158
 
159
+ def test_finds_openai_responses_tool_outputs(self):
160
+ body = {"model": "gpt-5", "input": [
161
+ {"role": "user", "content": "do something"},
162
+ {"role": "assistant", "content": [{"type": "output_text", "text": "calling"}]},
163
+ {"type": "function_call", "call_id": "fc1", "name": "read", "arguments": "{}"},
164
+ {"type": "function_call_output", "call_id": "fc1", "output": "x" * 10000},
165
+ {"role": "assistant", "content": [{"type": "output_text", "text": "done"}]},
166
+ {"role": "user", "content": "thanks"},
167
+ ]}
168
+ fmt = detect_format(body)
169
+ items = scan_reducible_items(body, fmt)
170
+ tr_items = [i for i in items if i.category in ("tool_result", "tool_result_last2")]
171
+ assert len(tr_items) == 1
172
+ assert tr_items[0].msg_index == 3
173
+
174
+ def test_finds_openai_chat_tool_outputs(self):
175
+ body = {"model": "gpt-4", "messages": [
176
+ {"role": "user", "content": "do something"},
177
+ {"role": "assistant", "content": None, "tool_calls": [
178
+ {"id": "t1", "type": "function", "function": {"name": "read", "arguments": "{}"}}
179
+ ]},
180
+ {"role": "tool", "tool_call_id": "t1", "content": "x" * 10000},
181
+ {"role": "assistant", "content": "done"},
182
+ {"role": "user", "content": "thanks"},
183
+ ]}
184
+ fmt = detect_format(body)
185
+ items = scan_reducible_items(body, fmt)
186
+ tr_items = [i for i in items if i.category in ("tool_result", "tool_result_last2")]
187
+ assert len(tr_items) == 1
188
+ assert tr_items[0].msg_index == 2
189
+
190
+ def test_finds_openai_responses_conversation_text(self):
191
+ """Verify we find text in output_text blocks, not just string content."""
192
+ body = {"model": "gpt-5", "input": [
193
+ {"role": "user", "content": "msg " + "x" * 2000},
194
+ {"role": "assistant", "content": [{"type": "output_text", "text": "resp " + "y" * 2000}]},
195
+ {"role": "user", "content": "current"},
196
+ ]}
197
+ fmt = detect_format(body)
198
+ items = scan_reducible_items(body, fmt)
199
+ text_items = [i for i in items if i.category == "conversation_text"]
200
+ # Should find both the user string content AND the assistant output_text block
201
+ assert len(text_items) >= 2
202
+
154
203
 
155
204
  class TestApplyReduction:
156
205
  def test_remove_thinking_signature(self):
@@ -351,3 +400,161 @@ class TestEnforcePayloadBudget:
351
400
  fmt = self._fmt()
352
401
  result, reductions, freed = enforce_payload_budget(body, fmt, 1)
353
402
  assert reductions <= 2
403
+
404
+ def test_reduces_openai_responses_payload(self):
405
+ items = []
406
+ for i in range(20):
407
+ items.append({"role": "user", "content": "msg " + "x" * 2000})
408
+ items.append({"role": "assistant", "content": [{"type": "output_text", "text": "resp " + "y" * 2000}]})
409
+ items.append({"role": "user", "content": "current"})
410
+ body = {"model": "gpt-5", "input": items}
411
+ fmt = detect_format(body)
412
+ fmt_copy = copy.copy(fmt)
413
+ fmt_copy.set_token_counter(lambda text: len(text) // 4)
414
+ result, reductions, freed = enforce_payload_budget(body, fmt_copy, 5000)
415
+ assert reductions > 0
416
+
417
+
418
+ class TestSmallPayloadProtection:
419
+ """P1-1: With 1-2 turn groups, ALL tool results must be in the last2 protected window."""
420
+
421
+ def test_single_tool_turn_all_protected(self):
422
+ """A payload with one tool chain turn — tool result must be tool_result_last2."""
423
+ msgs = [
424
+ {"role": "user", "content": "do something"},
425
+ {"role": "assistant", "content": [
426
+ {"type": "tool_use", "id": "t1", "name": "Read", "input": {}},
427
+ ]},
428
+ {"role": "user", "content": [
429
+ {"type": "tool_result", "tool_use_id": "t1", "content": "x" * 10000},
430
+ ]},
431
+ {"role": "assistant", "content": [{"type": "text", "text": "done"}]},
432
+ ]
433
+ body = _make_payload(msgs)
434
+ fmt = detect_format(body)
435
+ items = scan_reducible_items(body, fmt)
436
+ tr_items = [i for i in items if i.category.startswith("tool_result")]
437
+ assert len(tr_items) == 1
438
+ # With only 1 turn group, the tool result must be in the protected window.
439
+ assert tr_items[0].category == "tool_result_last2"
440
+
441
+ def test_two_turns_all_protected(self):
442
+ """A payload with 2 turn groups — tool results in both must be last2."""
443
+ msgs = [
444
+ {"role": "user", "content": "do something"},
445
+ {"role": "assistant", "content": [
446
+ {"type": "tool_use", "id": "t1", "name": "Read", "input": {}},
447
+ ]},
448
+ {"role": "user", "content": [
449
+ {"type": "tool_result", "tool_use_id": "t1", "content": "x" * 10000},
450
+ ]},
451
+ {"role": "assistant", "content": [{"type": "text", "text": "done"}]},
452
+ {"role": "user", "content": "thanks"},
453
+ {"role": "assistant", "content": "welcome"},
454
+ ]
455
+ body = _make_payload(msgs)
456
+ fmt = detect_format(body)
457
+ items = scan_reducible_items(body, fmt)
458
+ tr_items = [i for i in items if i.category.startswith("tool_result")]
459
+ assert len(tr_items) == 1
460
+ assert tr_items[0].category == "tool_result_last2"
461
+
462
+ def test_no_unprotected_tool_results_with_small_payload(self):
463
+ """With <=2 turns, no tool_result items should exist in the unprotected category."""
464
+ msgs = [
465
+ {"role": "user", "content": "do something"},
466
+ {"role": "assistant", "content": [
467
+ {"type": "tool_use", "id": "t1", "name": "Read", "input": {}},
468
+ ]},
469
+ {"role": "user", "content": [
470
+ {"type": "tool_result", "tool_use_id": "t1", "content": "x" * 10000},
471
+ ]},
472
+ {"role": "assistant", "content": [{"type": "text", "text": "done"}]},
473
+ {"role": "user", "content": "current"},
474
+ ]
475
+ body = _make_payload(msgs)
476
+ fmt = detect_format(body)
477
+ items = scan_reducible_items(body, fmt)
478
+ unprotected = [i for i in items if i.category == "tool_result"]
479
+ assert len(unprotected) == 0
480
+
481
+
482
+ class TestGeminiMediaScan:
483
+ """P1-3: Gemini inline_data images must be detected by scan_reducible_items."""
484
+
485
+ def test_finds_gemini_inline_data_image(self):
486
+ body = {"contents": [
487
+ {"role": "user", "parts": [
488
+ {"inline_data": {"mime_type": "image/png", "data": "A" * 50000}},
489
+ {"text": "what is this?"},
490
+ ]},
491
+ {"role": "model", "parts": [{"text": "It is an image."}]},
492
+ {"role": "user", "parts": [{"text": "q2"}]},
493
+ {"role": "model", "parts": [{"text": "a2"}]},
494
+ {"role": "user", "parts": [{"text": "current"}]},
495
+ ]}
496
+ fmt = detect_format(body)
497
+ items = scan_reducible_items(body, fmt)
498
+ image_items = [i for i in items if i.category == "image"]
499
+ assert len(image_items) == 1
500
+ assert image_items[0].size_bytes == 50000
501
+
502
+ def test_skips_small_gemini_image(self):
503
+ body = {"contents": [
504
+ {"role": "user", "parts": [
505
+ {"inline_data": {"mime_type": "image/png", "data": "A" * 100}},
506
+ ]},
507
+ {"role": "model", "parts": [{"text": "tiny."}]},
508
+ ]}
509
+ fmt = detect_format(body)
510
+ items = scan_reducible_items(body, fmt)
511
+ assert not any(i.category == "image" for i in items)
512
+
513
+
514
+ class TestResponsesInputImageScan:
515
+ """P1-2: OpenAI Responses input_image blocks must be yielded by iter_media_blocks."""
516
+
517
+ def test_iter_media_blocks_finds_input_image(self):
518
+ body = {"model": "gpt-5", "input": [
519
+ {"role": "user", "content": [
520
+ {"type": "input_image", "image_url": f"data:image/png;base64,{'A' * 50000}"},
521
+ {"type": "input_text", "text": "describe this"},
522
+ ]},
523
+ {"role": "assistant", "content": [{"type": "output_text", "text": "a photo"}]},
524
+ ]}
525
+ fmt = detect_format(body)
526
+ media_blocks = list(fmt.iter_media_blocks(body))
527
+ assert len(media_blocks) == 1
528
+ assert media_blocks[0].msg_index == 0
529
+ assert media_blocks[0].block_index == 0
530
+
531
+ def test_input_image_replace_uses_input_text_for_user(self):
532
+ body = {"model": "gpt-5", "input": [
533
+ {"role": "user", "content": [
534
+ {"type": "input_image", "image_url": f"data:image/png;base64,{'A' * 50000}"},
535
+ ]},
536
+ {"role": "assistant", "content": [{"type": "output_text", "text": "done"}]},
537
+ ]}
538
+ fmt = detect_format(body)
539
+ for media_info in fmt.iter_media_blocks(body):
540
+ media_info.replace_with_text("[image removed]")
541
+ break
542
+ replaced = body["input"][0]["content"][0]
543
+ assert replaced["type"] == "input_text"
544
+ assert replaced["text"] == "[image removed]"
545
+
546
+ def test_scan_finds_input_image_as_image_category(self):
547
+ body = {"model": "gpt-5", "input": [
548
+ {"role": "user", "content": [
549
+ {"type": "input_image", "image_url": f"data:image/png;base64,{'A' * 50000}"},
550
+ ]},
551
+ {"role": "assistant", "content": [{"type": "output_text", "text": "a photo"}]},
552
+ {"role": "user", "content": "q2"},
553
+ {"role": "assistant", "content": [{"type": "output_text", "text": "a2"}]},
554
+ {"role": "user", "content": "current"},
555
+ ]}
556
+ fmt = detect_format(body)
557
+ items = scan_reducible_items(body, fmt)
558
+ image_items = [i for i in items if i.category == "image"]
559
+ assert len(image_items) == 1
560
+ assert image_items[0].size_bytes == 50000