virtual-context 0.2.7__tar.gz → 0.2.9__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.
- {virtual_context-0.2.7 → virtual_context-0.2.9}/PKG-INFO +13 -3
- {virtual_context-0.2.7 → virtual_context-0.2.9}/README.md +12 -2
- {virtual_context-0.2.7 → virtual_context-0.2.9}/pyproject.toml +1 -1
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_cli_init.py +40 -0
- virtual_context-0.2.9/tests/test_fact_redesign.py +95 -0
- virtual_context-0.2.9/tests/test_fill_pass.py +274 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_format_agnostic.py +3 -3
- virtual_context-0.2.9/tests/test_store_recovery.py +192 -0
- virtual_context-0.2.9/virtual-context.yaml +109 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/__init__.py +1 -1
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/cli/main.py +95 -4
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/config.py +1 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/assembler.py +70 -33
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/compaction_pipeline.py +8 -2
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/compactor.py +12 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/composite_store.py +9 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/conversation_store.py +1 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/protocols.py +3 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/retriever.py +12 -5
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/store.py +16 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/tag_generator.py +8 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/engine.py +1 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/dashboard.py +1 -1
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/formats.py +247 -51
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/handlers.py +22 -7
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/message_filter.py +365 -10
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/metrics.py +25 -1
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/server.py +127 -30
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/state.py +2 -2
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/storage/falkordb.py +9 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/storage/filesystem.py +9 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/storage/neo4j.py +11 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/storage/postgres.py +32 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/storage/sqlite.py +83 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/types.py +8 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/.gitignore +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/LICENSE +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/assets/dashboard.png +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/assets/hero.png +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/models.yaml +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/REGRESSION_MAP.md +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/conftest.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/docker-compose.test.yml +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/haiku/__init__.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/haiku/conftest.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/haiku/test_compaction.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/haiku/test_retrieval.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/haiku/test_tagging.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/ollama/__init__.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/ollama/conftest.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/ollama/test_compactor.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/ollama/test_pipeline.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/ollama/test_provider.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/ollama/test_tag_generator.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/proxy/__init__.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/proxy/test_dashboard_cors.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/proxy/test_metrics.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_assembler.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_backend_integration.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_budget_enforcement.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_compaction_commit_prune.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_compactor.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_compactor_concurrent.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_composite_store.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_config.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_context_bleed.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_conversation_identity.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_conversation_lifecycle.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_conversation_scoping.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_embedding_tag_generator.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_empty_turn_skip.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_engine_integration.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_engine_lookback.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_engine_state.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_fact_enrichment.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_fact_graph_integration.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_fact_link_checker.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_fact_link_query.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_fact_link_types.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_fact_links_sqlite.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_find_quote.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_headless.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_history_filter.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_idf_retrieval.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_ingest_index_integrity.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_longmemeval_auth.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_mcp_server.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_media.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_message_filter.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_metrics_persistence.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_model_catalog.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_model_limits.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_monitor.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_multi_instance.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_noop_fact_link_store.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_openrouter_provider.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_paging.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_passthrough_filter.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_presets.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_prev_context_leak.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_provider_adapters.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_proxy.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_proxy_dashboard.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_proxy_formats.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_proxy_message_filter.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_proxy_session.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_proxy_streaming.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_raw_content.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_recall_all.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_registry_lifecycle.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_request_captures_persistence.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_retriever.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_rrf_scoring.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_segmenter.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_semantic_search.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_sender_identity.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_session_cache.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_session_date.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_storage_protocols.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_store_sqlite.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_stub_turn_handling.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_supersession.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_supersession_migration.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_tag_canonicalizer.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_tag_consolidator.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_tag_generator.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_tag_splitter.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_telemetry.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_telemetry_integration.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_tool_loop.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_tool_output_interceptor.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_tool_result_filter.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_tool_tags.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_tui.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_turn_grouping.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_turn_tag_index.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_unified_budget.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_upstream_trim.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_verb_expansion.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual-context.yaml.example +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/cli/__init__.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/conversation_identity.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/__init__.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/embedding_provider.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/embedding_tag_generator.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/engine_utils.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/fact_query.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/fts_preprocessor.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/hint_builder.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/llm_utils.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/math_utils.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/model_catalog.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/monitor.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/paging_manager.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/provider_adapters.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/quote_search.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/retrieval_assembler.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/retrieval_scoring.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/search_engine.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/segmenter.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/semantic_search.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/tag_canonicalizer.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/tag_consolidator.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/tag_scoring.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/tag_splitter.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/tagging_pipeline.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/telemetry.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/temporal_resolver.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/tool_loop.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/tool_query.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/turn_tag_index.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/data/anthropic-tokenizer/tokenizer.json +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/ingest/__init__.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/ingest/curator.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/ingest/date_resolver.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/ingest/parsers.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/ingest/supersession.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/mcp/__init__.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/mcp/server.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/model_limits.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/openclaw/virtual-context.mjs +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/patterns.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/presets/__init__.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/presets/agentic.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/presets/base.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/presets/coding.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/providers/__init__.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/providers/anthropic.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/providers/base.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/providers/generic_openai.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/providers/ollama_native.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/__init__.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/_envelope.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/dashboard.html +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/helpers.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/media.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/multi.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/registry.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/session_cache.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/static/android-chrome-192x192.png +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/static/android-chrome-512x512.png +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/static/apple-touch-icon.png +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/static/favicon-16x16.png +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/static/favicon-32x32.png +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/static/favicon.ico +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/static/site.webmanifest +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/tool_output_interceptor.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/storage/__init__.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/storage/helpers.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/storage/noop_fact_link_store.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/token_counter.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/__init__.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/app.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/chat.tcss +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/chat_provider.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/headless.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/modals/__init__.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/modals/turn_inspector.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/state.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/widgets/__init__.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/widgets/budget_bar.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/widgets/chat_view.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/widgets/input_box.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/widgets/tag_panel.py +0 -0
- {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/widgets/turn_list.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: virtual-context
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
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
|
|
@@ -180,10 +180,16 @@ proxy:
|
|
|
180
180
|
**Daemon mode:** run as a background service:
|
|
181
181
|
|
|
182
182
|
```bash
|
|
183
|
-
|
|
183
|
+
# Creates config if needed, installs + starts daemon
|
|
184
|
+
virtual-context daemon install --upstream https://api.anthropic.com
|
|
185
|
+
|
|
186
|
+
# Or: guided interactive setup with daemon
|
|
187
|
+
virtual-context onboard --wizard --install-daemon
|
|
184
188
|
```
|
|
185
189
|
|
|
186
|
-
Daemon
|
|
190
|
+
Daemon lifecycle: `daemon status | start | stop | restart | uninstall`
|
|
191
|
+
|
|
192
|
+
Full setup docs (macOS `launchd`, Linux `systemd --user`, Windows Task Scheduler): [`docs/install.md`](docs/install.md)
|
|
187
193
|
|
|
188
194
|
### Python SDK
|
|
189
195
|
|
|
@@ -639,10 +645,14 @@ virtual-context transform -m "What about auth?"# tag + retrieve + assemble
|
|
|
639
645
|
virtual-context aliases list # show all tag aliases
|
|
640
646
|
virtual-context aliases suggest # auto-detect potential aliases
|
|
641
647
|
virtual-context aliases add db database # register alias manually
|
|
648
|
+
virtual-context init coding # create config from preset
|
|
649
|
+
virtual-context onboard # guided setup (create/validate config)
|
|
650
|
+
virtual-context onboard --wizard # interactive multi-instance setup
|
|
642
651
|
virtual-context proxy -u https://api.anthropic.com # single-instance proxy
|
|
643
652
|
virtual-context proxy # multi-instance (from config)
|
|
644
653
|
virtual-context presets list # list available config presets
|
|
645
654
|
virtual-context presets show coding # dump preset config as YAML
|
|
655
|
+
virtual-context daemon install # install daemon/service
|
|
646
656
|
virtual-context daemon status # service status (platform-specific)
|
|
647
657
|
virtual-context daemon start # start/enable daemon
|
|
648
658
|
virtual-context daemon stop # stop daemon
|
|
@@ -133,10 +133,16 @@ proxy:
|
|
|
133
133
|
**Daemon mode:** run as a background service:
|
|
134
134
|
|
|
135
135
|
```bash
|
|
136
|
-
|
|
136
|
+
# Creates config if needed, installs + starts daemon
|
|
137
|
+
virtual-context daemon install --upstream https://api.anthropic.com
|
|
138
|
+
|
|
139
|
+
# Or: guided interactive setup with daemon
|
|
140
|
+
virtual-context onboard --wizard --install-daemon
|
|
137
141
|
```
|
|
138
142
|
|
|
139
|
-
Daemon
|
|
143
|
+
Daemon lifecycle: `daemon status | start | stop | restart | uninstall`
|
|
144
|
+
|
|
145
|
+
Full setup docs (macOS `launchd`, Linux `systemd --user`, Windows Task Scheduler): [`docs/install.md`](docs/install.md)
|
|
140
146
|
|
|
141
147
|
### Python SDK
|
|
142
148
|
|
|
@@ -592,10 +598,14 @@ virtual-context transform -m "What about auth?"# tag + retrieve + assemble
|
|
|
592
598
|
virtual-context aliases list # show all tag aliases
|
|
593
599
|
virtual-context aliases suggest # auto-detect potential aliases
|
|
594
600
|
virtual-context aliases add db database # register alias manually
|
|
601
|
+
virtual-context init coding # create config from preset
|
|
602
|
+
virtual-context onboard # guided setup (create/validate config)
|
|
603
|
+
virtual-context onboard --wizard # interactive multi-instance setup
|
|
595
604
|
virtual-context proxy -u https://api.anthropic.com # single-instance proxy
|
|
596
605
|
virtual-context proxy # multi-instance (from config)
|
|
597
606
|
virtual-context presets list # list available config presets
|
|
598
607
|
virtual-context presets show coding # dump preset config as YAML
|
|
608
|
+
virtual-context daemon install # install daemon/service
|
|
599
609
|
virtual-context daemon status # service status (platform-specific)
|
|
600
610
|
virtual-context daemon start # start/enable daemon
|
|
601
611
|
virtual-context daemon stop # stop daemon
|
|
@@ -97,6 +97,46 @@ def test_daemon_restart_in_help(tmp_cwd):
|
|
|
97
97
|
assert "restart" in result.stdout
|
|
98
98
|
|
|
99
99
|
|
|
100
|
+
def test_daemon_not_installed_guard(tmp_cwd):
|
|
101
|
+
"""daemon commands should fail with a helpful message when no daemon is installed."""
|
|
102
|
+
env = {**__import__("os").environ, "HOME": str(tmp_cwd)}
|
|
103
|
+
for action in ("status", "start", "stop", "restart"):
|
|
104
|
+
result = subprocess.run(
|
|
105
|
+
[sys.executable, "-m", "virtual_context.cli.main", "daemon", action],
|
|
106
|
+
capture_output=True,
|
|
107
|
+
text=True,
|
|
108
|
+
env=env,
|
|
109
|
+
)
|
|
110
|
+
assert result.returncode != 0, f"daemon {action} should fail when not installed"
|
|
111
|
+
assert "not installed" in result.stdout.lower(), (
|
|
112
|
+
f"daemon {action} should mention 'not installed'"
|
|
113
|
+
)
|
|
114
|
+
assert "daemon install" in result.stdout, (
|
|
115
|
+
f"daemon {action} should suggest 'daemon install'"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_daemon_install_creates_config(tmp_cwd):
|
|
120
|
+
"""daemon install should auto-create config from agentic preset if none exists."""
|
|
121
|
+
config_path = tmp_cwd / "virtual-context.yaml"
|
|
122
|
+
assert not config_path.exists()
|
|
123
|
+
# daemon install will fail on launchctl/systemctl, but should create config first
|
|
124
|
+
result = _run_cli("daemon", "install")
|
|
125
|
+
assert config_path.exists(), "daemon install should create config"
|
|
126
|
+
assert "Created config" in result.stdout
|
|
127
|
+
content = config_path.read_text()
|
|
128
|
+
assert "tag_generator" in content
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_daemon_install_preserves_existing_config(tmp_cwd):
|
|
132
|
+
"""daemon install should not overwrite an existing config."""
|
|
133
|
+
config_path = tmp_cwd / "virtual-context.yaml"
|
|
134
|
+
config_path.write_text("custom: true\n")
|
|
135
|
+
result = _run_cli("daemon", "install")
|
|
136
|
+
assert config_path.read_text() == "custom: true\n"
|
|
137
|
+
assert "Created config" not in result.stdout
|
|
138
|
+
|
|
139
|
+
|
|
100
140
|
def test_presets_list(tmp_cwd):
|
|
101
141
|
result = _run_cli("presets", "list")
|
|
102
142
|
assert result.returncode == 0
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Tests for fact extraction redesign."""
|
|
2
|
+
from virtual_context.types import CompactorConfig
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_compactor_config_has_code_mode():
|
|
6
|
+
cc = CompactorConfig()
|
|
7
|
+
assert hasattr(cc, "code_mode")
|
|
8
|
+
assert cc.code_mode is True
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_config_loader_reads_code_mode():
|
|
12
|
+
from virtual_context.config import load_config
|
|
13
|
+
import tempfile, os, yaml
|
|
14
|
+
cfg = {"compaction": {"code_mode": False}, "context_window": 100000}
|
|
15
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
|
|
16
|
+
yaml.dump(cfg, f)
|
|
17
|
+
tmp_path = f.name
|
|
18
|
+
try:
|
|
19
|
+
config = load_config(tmp_path)
|
|
20
|
+
assert config.compactor.code_mode is False
|
|
21
|
+
finally:
|
|
22
|
+
os.unlink(tmp_path)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_replace_facts_for_segment_default():
|
|
26
|
+
from virtual_context.core.store import ContextStore
|
|
27
|
+
|
|
28
|
+
class MinimalStore(ContextStore):
|
|
29
|
+
"""Minimal concrete subclass that only implements abstract methods as no-ops."""
|
|
30
|
+
def store_segment(self, segment): return ""
|
|
31
|
+
def get_segment(self, ref, *, conversation_id=None): return None
|
|
32
|
+
def get_summary(self, ref, *, conversation_id=None): return None
|
|
33
|
+
def get_summaries_by_tags(self, tags, min_overlap=1, limit=10, before=None, after=None, conversation_id=None): return []
|
|
34
|
+
def search(self, query, tags=None, limit=5, conversation_id=None): return []
|
|
35
|
+
def get_all_tags(self, conversation_id=None): return []
|
|
36
|
+
def get_conversation_stats(self): return []
|
|
37
|
+
def get_tag_aliases(self, conversation_id=None): return {}
|
|
38
|
+
def set_tag_alias(self, alias, canonical, conversation_id=""): pass
|
|
39
|
+
def delete_segment(self, ref): return False
|
|
40
|
+
def cleanup(self, max_age=None, max_total_tokens=None): return 0
|
|
41
|
+
def save_tag_summary(self, tag_summary, conversation_id=""): pass
|
|
42
|
+
def get_tag_summary(self, tag, conversation_id=""): return None
|
|
43
|
+
def get_all_tag_summaries(self, *, conversation_id=None): return []
|
|
44
|
+
def search_full_text(self, query, limit=5, conversation_id=None): return []
|
|
45
|
+
def get_segments_by_tags(self, tags, min_overlap=1, limit=20, conversation_id=None): return []
|
|
46
|
+
|
|
47
|
+
store = MinimalStore()
|
|
48
|
+
deleted, inserted = store.replace_facts_for_segment("conv1", "seg1", [])
|
|
49
|
+
assert deleted == 0
|
|
50
|
+
assert inserted == 0
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_compaction_uses_replace_facts_for_segment():
|
|
54
|
+
"""Verify the compaction pipeline calls replace_facts_for_segment."""
|
|
55
|
+
import inspect
|
|
56
|
+
from virtual_context.core import compaction_pipeline
|
|
57
|
+
source = inspect.getsource(compaction_pipeline)
|
|
58
|
+
assert "replace_facts_for_segment" in source, \
|
|
59
|
+
"compaction_pipeline must call replace_facts_for_segment"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_code_mode_prompt_appended():
|
|
63
|
+
"""When code_mode is True, the compactor prompt should include the code mode block."""
|
|
64
|
+
from virtual_context.core.compactor import CODE_MODE_FACT_PROMPT
|
|
65
|
+
assert "Do NOT extract intermediary" in CODE_MODE_FACT_PROMPT
|
|
66
|
+
assert "conclusions, findings, discoveries" in CODE_MODE_FACT_PROMPT
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_tag_generator_accepts_code_mode():
|
|
70
|
+
from virtual_context.core.tag_generator import LLMTagGenerator
|
|
71
|
+
from virtual_context.types import TagGeneratorConfig
|
|
72
|
+
from unittest.mock import MagicMock
|
|
73
|
+
gen = LLMTagGenerator(llm_provider=MagicMock(), config=TagGeneratorConfig(), code_mode=True)
|
|
74
|
+
assert gen._code_mode is True
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_build_tag_generator_passes_code_mode():
|
|
78
|
+
from virtual_context.core.tag_generator import build_tag_generator, LLMTagGenerator
|
|
79
|
+
from virtual_context.types import TagGeneratorConfig
|
|
80
|
+
from unittest.mock import MagicMock
|
|
81
|
+
config = TagGeneratorConfig(type="llm", provider="openrouter", model="test")
|
|
82
|
+
gen = build_tag_generator(config, MagicMock(), code_mode=True)
|
|
83
|
+
assert isinstance(gen, LLMTagGenerator)
|
|
84
|
+
assert gen._code_mode is True
|
|
85
|
+
gen2 = build_tag_generator(config, MagicMock())
|
|
86
|
+
assert gen2._code_mode is False
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_engine_passes_code_mode_to_tag_generator():
|
|
90
|
+
import inspect
|
|
91
|
+
from virtual_context import engine
|
|
92
|
+
source = inspect.getsource(engine)
|
|
93
|
+
assert "code_mode" in source, "engine.py must reference code_mode"
|
|
94
|
+
assert "compactor.code_mode" in source or "config.compactor.code_mode" in source, \
|
|
95
|
+
"engine.py must read code_mode from compactor config"
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""Tests for the fill pass pipeline stage."""
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
from unittest.mock import MagicMock
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
|
|
9
|
+
from virtual_context.proxy.formats import detect_format
|
|
10
|
+
from virtual_context.types import (
|
|
11
|
+
RetrievalResult, StoredSummary, AssembledContext, TagSummary, SegmentMetadata,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_retrieval_result_has_overflow_field():
|
|
16
|
+
rr = RetrievalResult()
|
|
17
|
+
assert hasattr(rr, "overflow_summaries")
|
|
18
|
+
assert rr.overflow_summaries == []
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_retrieval_result_overflow_accepts_summaries():
|
|
22
|
+
s = StoredSummary(ref="seg_001", primary_tag="cooking", summary="test", summary_tokens=50)
|
|
23
|
+
rr = RetrievalResult(overflow_summaries=[s])
|
|
24
|
+
assert len(rr.overflow_summaries) == 1
|
|
25
|
+
assert rr.overflow_summaries[0].ref == "seg_001"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_assembled_context_has_retrieval_result():
|
|
29
|
+
ac = AssembledContext()
|
|
30
|
+
assert ac.retrieval_result is None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_assembled_context_has_presented_tags():
|
|
34
|
+
ac = AssembledContext()
|
|
35
|
+
assert isinstance(ac.presented_tags, set)
|
|
36
|
+
assert len(ac.presented_tags) == 0
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_assembled_context_presented_tags_populated():
|
|
40
|
+
ac = AssembledContext(presented_tags={"cooking", "baking", "recipes"})
|
|
41
|
+
assert "cooking" in ac.presented_tags
|
|
42
|
+
assert len(ac.presented_tags) == 3
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_format_tag_section_standalone():
|
|
46
|
+
"""Standalone format_tag_section produces the same XML format as the assembler."""
|
|
47
|
+
from virtual_context.core.assembler import format_tag_section
|
|
48
|
+
from virtual_context.types import StoredSummary, SegmentMetadata
|
|
49
|
+
from datetime import datetime, timezone
|
|
50
|
+
|
|
51
|
+
s1 = StoredSummary(
|
|
52
|
+
ref="s1", primary_tag="cooking", summary="Italian cooking techniques",
|
|
53
|
+
summary_tokens=100, tags=["cooking", "italian"],
|
|
54
|
+
metadata=SegmentMetadata(),
|
|
55
|
+
start_timestamp=datetime.now(timezone.utc),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
result = format_tag_section("cooking", [s1])
|
|
59
|
+
assert '<virtual-context tags="cooking, italian"' in result
|
|
60
|
+
assert "[1/1]" in result
|
|
61
|
+
assert "Italian cooking techniques" in result
|
|
62
|
+
assert "</virtual-context>" in result
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _make_summary(ref: str, tag: str, tokens: int) -> StoredSummary:
|
|
66
|
+
return StoredSummary(
|
|
67
|
+
ref=ref, primary_tag=tag, tags=[tag],
|
|
68
|
+
summary=f"Summary for {tag}", summary_tokens=tokens,
|
|
69
|
+
metadata=SegmentMetadata(),
|
|
70
|
+
start_timestamp=datetime.now(timezone.utc),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _make_anthropic_body(user_texts: list[str]) -> dict:
|
|
75
|
+
messages = []
|
|
76
|
+
for text in user_texts:
|
|
77
|
+
messages.append({"role": "user", "content": text})
|
|
78
|
+
messages.append({"role": "assistant", "content": [{"type": "text", "text": f"Reply to: {text}"}]})
|
|
79
|
+
messages.append({"role": "user", "content": "current question"})
|
|
80
|
+
return {"system": "You are helpful.", "messages": messages, "model": "claude-opus-4-6"}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_fill_pass_no_op_when_at_target():
|
|
84
|
+
from virtual_context.proxy.message_filter import fill_pass
|
|
85
|
+
body = _make_anthropic_body(["hello", "world"])
|
|
86
|
+
fmt = detect_format(body)
|
|
87
|
+
result, summaries, turns = fill_pass(
|
|
88
|
+
body=body, fmt=fmt, outbound_tokens=90000, target_tokens=90000,
|
|
89
|
+
assembled=None, pre_filter_body=body,
|
|
90
|
+
store=MagicMock(), conversation_id="test",
|
|
91
|
+
)
|
|
92
|
+
assert summaries == 0
|
|
93
|
+
assert turns == 0
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_fill_pass_no_op_when_over_target():
|
|
97
|
+
from virtual_context.proxy.message_filter import fill_pass
|
|
98
|
+
body = _make_anthropic_body(["hello"])
|
|
99
|
+
fmt = detect_format(body)
|
|
100
|
+
result, summaries, turns = fill_pass(
|
|
101
|
+
body=body, fmt=fmt, outbound_tokens=100000, target_tokens=90000,
|
|
102
|
+
assembled=None, pre_filter_body=body,
|
|
103
|
+
store=MagicMock(), conversation_id="test",
|
|
104
|
+
)
|
|
105
|
+
assert summaries == 0
|
|
106
|
+
assert turns == 0
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_fill_pass_adds_breadth_summaries():
|
|
110
|
+
from virtual_context.proxy.message_filter import fill_pass
|
|
111
|
+
|
|
112
|
+
body = _make_anthropic_body(["hello"])
|
|
113
|
+
fmt = detect_format(body)
|
|
114
|
+
|
|
115
|
+
ts1 = TagSummary(tag="cooking", summary="Italian cooking", summary_tokens=50,
|
|
116
|
+
source_segment_refs=["seg_a"], updated_at=datetime.now(timezone.utc))
|
|
117
|
+
ts2 = TagSummary(tag="baking", summary="Bread baking", summary_tokens=50,
|
|
118
|
+
source_segment_refs=["seg_b"], updated_at=datetime.now(timezone.utc))
|
|
119
|
+
mock_store = MagicMock()
|
|
120
|
+
mock_store.get_all_tag_summaries.return_value = [ts1, ts2]
|
|
121
|
+
|
|
122
|
+
assembled = AssembledContext(
|
|
123
|
+
presented_segment_refs=set(),
|
|
124
|
+
presented_tags=set(),
|
|
125
|
+
tag_sections={},
|
|
126
|
+
retrieval_result=RetrievalResult(),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
result, summaries_added, turns_added = fill_pass(
|
|
130
|
+
body=body, fmt=fmt, outbound_tokens=70000, target_tokens=90000,
|
|
131
|
+
assembled=assembled, pre_filter_body=copy.deepcopy(body),
|
|
132
|
+
store=mock_store, conversation_id="test",
|
|
133
|
+
summary_ratio=1.0,
|
|
134
|
+
)
|
|
135
|
+
assert summaries_added >= 1
|
|
136
|
+
mock_store.get_all_tag_summaries.assert_called_once_with(conversation_id="test")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_presented_tags_from_segments_and_full_sections():
|
|
140
|
+
"""presented_tags must include secondary tags from SEGMENTS/FULL sections."""
|
|
141
|
+
tag_sections = {
|
|
142
|
+
"cooking": (
|
|
143
|
+
'<virtual-context tags="cooking, italian" segments="2">\n'
|
|
144
|
+
"[1/2]\nPasta techniques\n\n---\n\n[2/2]\nRisotto methods\n"
|
|
145
|
+
"</virtual-context>"
|
|
146
|
+
),
|
|
147
|
+
"baking": (
|
|
148
|
+
'<virtual-context tags="baking, bread, sourdough" segments="1">\n'
|
|
149
|
+
"[1/1]\nSourdough starter maintenance\n"
|
|
150
|
+
"</virtual-context>"
|
|
151
|
+
),
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
_vc_tags_re = re.compile(r'<virtual-context\s+tags="([^"]*)"')
|
|
155
|
+
presented_tags: set[str] = set()
|
|
156
|
+
for section_text in tag_sections.values():
|
|
157
|
+
for m in _vc_tags_re.finditer(section_text):
|
|
158
|
+
for t in m.group(1).split(", "):
|
|
159
|
+
t = t.strip()
|
|
160
|
+
if t:
|
|
161
|
+
presented_tags.add(t)
|
|
162
|
+
# Also include section keys
|
|
163
|
+
presented_tags.update(tag_sections.keys())
|
|
164
|
+
|
|
165
|
+
assert "cooking" in presented_tags
|
|
166
|
+
assert "baking" in presented_tags
|
|
167
|
+
assert "italian" in presented_tags
|
|
168
|
+
assert "bread" in presented_tags
|
|
169
|
+
assert "sourdough" in presented_tags
|
|
170
|
+
assert "grilling" not in presented_tags
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def test_presented_tags_fallback_to_section_key():
|
|
174
|
+
"""When tags attr is empty, section key should still be covered."""
|
|
175
|
+
tag_sections = {
|
|
176
|
+
"misc": '<virtual-context tags="" segments="1">\n[1/1]\nRandom\n</virtual-context>',
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
_vc_tags_re = re.compile(r'<virtual-context\s+tags="([^"]*)"')
|
|
180
|
+
presented_tags: set[str] = set()
|
|
181
|
+
for section_text in tag_sections.values():
|
|
182
|
+
for m in _vc_tags_re.finditer(section_text):
|
|
183
|
+
for t in m.group(1).split(", "):
|
|
184
|
+
t = t.strip()
|
|
185
|
+
if t:
|
|
186
|
+
presented_tags.add(t)
|
|
187
|
+
presented_tags.update(tag_sections.keys())
|
|
188
|
+
|
|
189
|
+
# "misc" comes from keys(), not from regex (empty attr)
|
|
190
|
+
assert "misc" in presented_tags
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_fill_pass_accounting_summary_and_turns():
|
|
194
|
+
"""After fill pass, counts must match actual content added."""
|
|
195
|
+
from virtual_context.proxy.message_filter import fill_pass
|
|
196
|
+
|
|
197
|
+
body = _make_anthropic_body(["hello", "world"])
|
|
198
|
+
pre_filter = copy.deepcopy(body)
|
|
199
|
+
extra_msgs = [
|
|
200
|
+
{"role": "user", "content": "older message 1"},
|
|
201
|
+
{"role": "assistant", "content": [{"type": "text", "text": "older reply 1"}]},
|
|
202
|
+
{"role": "user", "content": "older message 2"},
|
|
203
|
+
{"role": "assistant", "content": [{"type": "text", "text": "older reply 2"}]},
|
|
204
|
+
]
|
|
205
|
+
pre_filter["messages"] = extra_msgs + pre_filter["messages"]
|
|
206
|
+
|
|
207
|
+
fmt = detect_format(body)
|
|
208
|
+
|
|
209
|
+
ts1 = TagSummary(tag="history", summary="Historical events discussed",
|
|
210
|
+
summary_tokens=30, source_segment_refs=["seg_h1"],
|
|
211
|
+
updated_at=datetime.now(timezone.utc))
|
|
212
|
+
mock_store = MagicMock()
|
|
213
|
+
mock_store.get_all_tag_summaries.return_value = [ts1]
|
|
214
|
+
|
|
215
|
+
assembled = AssembledContext(
|
|
216
|
+
presented_segment_refs=set(),
|
|
217
|
+
presented_tags=set(),
|
|
218
|
+
tag_sections={},
|
|
219
|
+
retrieval_result=RetrievalResult(),
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
result_body, summaries_added, turns_added = fill_pass(
|
|
223
|
+
body=body, fmt=fmt,
|
|
224
|
+
outbound_tokens=70000, target_tokens=90000,
|
|
225
|
+
assembled=assembled, pre_filter_body=pre_filter,
|
|
226
|
+
store=mock_store, conversation_id="test-conv",
|
|
227
|
+
summary_ratio=0.5,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
assert summaries_added >= 1
|
|
231
|
+
result_json = json.dumps(result_body)
|
|
232
|
+
assert "Historical events discussed" in result_json
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def test_fill_pass_sanitizes_restored_turns():
|
|
236
|
+
"""Restored turns must have thinking blocks stripped and media replaced."""
|
|
237
|
+
from virtual_context.proxy.message_filter import _sanitize_restored_turn
|
|
238
|
+
|
|
239
|
+
messages = [
|
|
240
|
+
{"role": "user", "content": [
|
|
241
|
+
{"type": "text", "text": "show me the image"},
|
|
242
|
+
{"type": "image", "source": {"type": "base64", "data": "abc123"}},
|
|
243
|
+
]},
|
|
244
|
+
{"role": "assistant", "content": [
|
|
245
|
+
{"type": "thinking", "thinking": "let me think..."},
|
|
246
|
+
{"type": "text", "text": "here is my answer"},
|
|
247
|
+
]},
|
|
248
|
+
]
|
|
249
|
+
|
|
250
|
+
sanitized = _sanitize_restored_turn(messages)
|
|
251
|
+
|
|
252
|
+
user_content = sanitized[0]["content"]
|
|
253
|
+
assert any(b.get("text") == "[image removed from restored turn]" for b in user_content)
|
|
254
|
+
assert not any(b.get("type") == "image" for b in user_content)
|
|
255
|
+
|
|
256
|
+
asst_content = sanitized[1]["content"]
|
|
257
|
+
assert not any(b.get("type") == "thinking" for b in asst_content)
|
|
258
|
+
assert any(b.get("text") == "here is my answer" for b in asst_content)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def test_fill_pass_no_restore_tool_references():
|
|
262
|
+
"""Media placeholders must NOT reference vc_restore_tool."""
|
|
263
|
+
from virtual_context.proxy.message_filter import _sanitize_restored_turn
|
|
264
|
+
|
|
265
|
+
messages = [
|
|
266
|
+
{"role": "user", "content": [
|
|
267
|
+
{"type": "input_image", "image_url": "data:image/png;base64,abc123"},
|
|
268
|
+
]},
|
|
269
|
+
]
|
|
270
|
+
|
|
271
|
+
sanitized = _sanitize_restored_turn(messages)
|
|
272
|
+
result_json = json.dumps(sanitized)
|
|
273
|
+
assert "vc_restore_tool" not in result_json
|
|
274
|
+
assert "image removed from restored turn" in result_json
|
|
@@ -450,7 +450,7 @@ class TestCollapseChainsCrossFormat:
|
|
|
450
450
|
fmt = detect_format(body)
|
|
451
451
|
tti = TurnTagIndex()
|
|
452
452
|
original_count = len(body["messages"])
|
|
453
|
-
result, collapsed, refs = collapse_turn_chains(
|
|
453
|
+
result, collapsed, refs, _ = collapse_turn_chains(
|
|
454
454
|
body, fmt, protected_recent_turns=3, turn_tag_index=tti,
|
|
455
455
|
store=None, conversation_id="test",
|
|
456
456
|
)
|
|
@@ -480,7 +480,7 @@ class TestCollapseChainsCrossFormat:
|
|
|
480
480
|
fmt = detect_format(body)
|
|
481
481
|
tti = TurnTagIndex()
|
|
482
482
|
original_count = len(body["input"])
|
|
483
|
-
result, collapsed, refs = collapse_turn_chains(
|
|
483
|
+
result, collapsed, refs, _ = collapse_turn_chains(
|
|
484
484
|
body, fmt, protected_recent_turns=3, turn_tag_index=tti,
|
|
485
485
|
store=None, conversation_id="test",
|
|
486
486
|
)
|
|
@@ -507,7 +507,7 @@ class TestCollapseChainsCrossFormat:
|
|
|
507
507
|
fmt = detect_format(body)
|
|
508
508
|
tti = TurnTagIndex()
|
|
509
509
|
original_count = len(body["messages"])
|
|
510
|
-
result, collapsed, refs = collapse_turn_chains(
|
|
510
|
+
result, collapsed, refs, _ = collapse_turn_chains(
|
|
511
511
|
body, fmt, protected_recent_turns=3, turn_tag_index=tti,
|
|
512
512
|
store=None, conversation_id="test",
|
|
513
513
|
)
|