From 01f9acfaf9b9bda124d69c9f5945bde948345347 Mon Sep 17 00:00:00 2001 From: makeitdouble Date: Wed, 3 Jun 2026 12:15:59 +0300 Subject: [PATCH] chore: refactor project structure, env config - reorganize runtime, agent, client, memory, and ui modules - add environment variable overrides for deploy-friendly config - update README.md --- README.md | 80 ++++---- agent/__init__.py | 0 agent/nodes/__init__.py | 1 + agents/base_node.py => agent/nodes/base.py | 0 agents/brain_node.py => agent/nodes/brain.py | 4 +- .../planner_node.py => agent/nodes/planner.py | 4 +- .../nodes/translation.py | 4 +- .../nodes/validation.py | 6 +- {agents => agent}/router.py | 0 agents/agent_runtime.py => agent/runtime.py | 10 +- agents/agent_state.py => agent/state.py | 0 app.py | 10 +- settings/app_settings.py => app_settings.py | 2 +- clients/__init__.py | 0 clients/brain_client.py | 8 +- {utils => clients}/errors.py | 0 clients/{clients_registry.py => registry.py} | 4 +- {utils => clients}/response_extractor.py | 0 clients/search_client.py | 4 +- utils/search.py => clients/search_provider.py | 0 clients/translation_client.py | 6 +- config_loader.py | 176 ++++++++++++++++++ emitter/runtime_emitter.py | 17 -- package.json | 2 +- runtime/__init__.py | 0 runtime/{runtime_client.py => client.py} | 4 +- runtime/{runtime_context.py => context.py} | 25 ++- {contracts => runtime}/context_contract.py | 0 memory/message_memory.py => runtime/memory.py | 8 +- runtime/registry.py | 3 + runtime/runtime_registry.py | 3 - memory/runtime_state.py => runtime/state.py | 2 +- .../state_sync.py | 6 +- runtime/{runtime_stream.py => stream.py} | 2 +- {utils => runtime}/telemetry.py | 2 +- settings/config_loader.py | 72 ------- static/images/highlight_v0.1.png | Bin 64675 -> 0 bytes {tools => tests}/probe_deep_thought.py | 8 +- {tools => tests}/run_translation_tests.py | 0 tests/test_agent_routing.py | 6 +- tests/test_brain_runtime_actions.py | 2 +- tests/test_config_loader.py | 118 +++++++++++- tests/test_message_memory.py | 8 +- tests/test_runtime_actions.py | 2 +- tests/test_runtime_stream_tokens.py | 6 +- tests/test_search_flow.py | 12 +- tests/test_translation_node.py | 6 +- tests/test_websocket_pending_usage.py | 2 +- {static => ui/static}/images/highlight.png | Bin .../images/jin-core-history-highlight.png | Bin .../images/jin-core-runtime-memory.png | Bin .../static}/images/jin-core-split-history.png | Bin .../static}/images/states/default.png | Bin {static => ui/static/js}/chat.js | 0 {static => ui/static/js}/dragdrop.js | 0 {static => ui/static/js}/logger.js | 0 {static => ui/static/js}/socket.js | 0 {static => ui/static/js}/status.js | 0 {static => ui/static/js}/telemetry.js | 0 {templates => ui/templates}/index.html | 12 +- utils/__init__.py | 0 utils/brain.py | 2 +- utils/runtime_actions.py | 2 +- utils/tokens.py | 2 +- utils/ws_errors.py | 2 +- websocket.py | 17 +- 66 files changed, 440 insertions(+), 232 deletions(-) create mode 100644 agent/__init__.py create mode 100644 agent/nodes/__init__.py rename agents/base_node.py => agent/nodes/base.py (100%) rename agents/brain_node.py => agent/nodes/brain.py (98%) rename agents/planner_node.py => agent/nodes/planner.py (92%) rename agents/translation_node.py => agent/nodes/translation.py (95%) rename agents/validation_node.py => agent/nodes/validation.py (95%) rename {agents => agent}/router.py (100%) rename agents/agent_runtime.py => agent/runtime.py (83%) rename agents/agent_state.py => agent/state.py (100%) rename settings/app_settings.py => app_settings.py (98%) create mode 100644 clients/__init__.py rename {utils => clients}/errors.py (100%) rename clients/{clients_registry.py => registry.py} (94%) rename {utils => clients}/response_extractor.py (100%) rename utils/search.py => clients/search_provider.py (100%) create mode 100644 config_loader.py delete mode 100644 emitter/runtime_emitter.py create mode 100644 runtime/__init__.py rename runtime/{runtime_client.py => client.py} (99%) rename runtime/{runtime_context.py => context.py} (85%) rename {contracts => runtime}/context_contract.py (100%) rename memory/message_memory.py => runtime/memory.py (99%) create mode 100644 runtime/registry.py delete mode 100644 runtime/runtime_registry.py rename memory/runtime_state.py => runtime/state.py (98%) rename utils/runtime_state_sync.py => runtime/state_sync.py (90%) rename runtime/{runtime_stream.py => stream.py} (99%) rename {utils => runtime}/telemetry.py (85%) delete mode 100644 settings/config_loader.py delete mode 100644 static/images/highlight_v0.1.png rename {tools => tests}/probe_deep_thought.py (96%) rename {tools => tests}/run_translation_tests.py (100%) rename {static => ui/static}/images/highlight.png (100%) rename {static => ui/static}/images/jin-core-history-highlight.png (100%) rename {static => ui/static}/images/jin-core-runtime-memory.png (100%) rename {static => ui/static}/images/jin-core-split-history.png (100%) rename {static => ui/static}/images/states/default.png (100%) rename {static => ui/static/js}/chat.js (100%) rename {static => ui/static/js}/dragdrop.js (100%) rename {static => ui/static/js}/logger.js (100%) rename {static => ui/static/js}/socket.js (100%) rename {static => ui/static/js}/status.js (100%) rename {static => ui/static/js}/telemetry.js (100%) rename {templates => ui/templates}/index.html (98%) create mode 100644 utils/__init__.py diff --git a/README.md b/README.md index 11b3a28c..a338de10 100644 --- a/README.md +++ b/README.md @@ -13,38 +13,22 @@ The engine is designed for multi-runtime local AI setups where the main reasonin Runtime memory can now influence conversation strategy, not just store facts. JIN tracks interaction patterns during a session and can adjust replies when the conversation starts looping instead of blindly restarting the same exchange. -![JIN Core Engine runtime UI](static/images/jin-core-split-history.png) +![JIN Core Engine runtime UI](ui/static/images/jin-core-split-history.png) -![highlight](static/images/highlight.png) +![highlight](ui/static/images/highlight.png) ## Capabilities -- FastAPI application with HTML UI at `/` and provider status at `/api/status`. -- WebSocket chat endpoint at `/ws/chat` with streaming output, logs, telemetry, and cancellation. -- OpenAI-compatible runtime clients for `/v1/chat/completions` and `/v1/models`. -- Separate runtime roles: `brain`, `service`, and `translator`. -- Optional `USE_SERVICE_AS_BRAIN` mode for running without a dedicated brain provider. -- Model-driven runtime actions, currently including web search requests emitted by the brain and executed by the runtime. -- Search result injection through trusted runtime context instead of raw chat history. -- Streaming lifecycle events for message start, thinking chunks, content chunks, completion, and errors. -- Reasoning/thinking chunks rendered separately from final assistant content. -- Runtime telemetry for model IDs, context windows, token usage, provider status, and runtime errors. -- Live runtime memory: a compact in-RAM state updated by the service model after each completed turn. -- Runtime memory panel in the right sidebar, showing the current memory state without XML tags. -- Runtime memory snapshots with arrow navigation for reviewing how the session state evolved. -- Runtime memory diff highlighting for new keys, changed keys, new values, and changed values. -- Runtime pattern memory that detects repeated interaction shapes and feeds behavior signals back into the brain prompt. -- Conversation activity signaling that helps JIN notice when an exchange is cooling down or stuck. -- No-signal and zero-diff handling so repeated empty turns can change response strategy instead of producing another generic prompt. -- Summarizer request logging with inspectable payloads for debugging what memory models received. -- Memory update animation: the settings panel glows during summarization and changed memory lines briefly flash before settling back into a unified state. -- Separate L1 and L2 memory update indicators for factual memory and pattern memory activity. -- Interrupted turn memory handling: aborted or incomplete responses are marked as unresolved state. -- Stream validation for repeated word loops, repeated sentences, repeated paragraphs, and leading HTML artifacts. -- Abort support that cancels the active task, closes active provider streams, and records interrupted memory. -- Agent runtime path for Cyrillic input: planner, internal translator, brain, validator. -- Direct brain route for non-Cyrillic input. -- Keyboard-first input: Enter sends, Ctrl/Shift+Enter inserts a newline, and the input field becomes the stop control during generation. +- A chat room that feels alive: answers stream in as they are written, thinking stays visually separate from the final reply, and you can stop a generation the moment it drifts. +- A visible short-term memory: JIN keeps a compact sense of what this session is about, what changed, and what still feels unresolved. +- A memory timeline you can inspect: step through snapshots and see which facts or patterns were added instead of guessing what the assistant remembered. +- A calmer loop breaker: when the conversation starts repeating itself, JIN can notice the pattern and change strategy instead of giving the same polite answer again. +- A built-in search move: the model can ask the runtime to search, then answer from trusted results without dumping raw tool syntax into the chat. +- A multilingual path that stays out of your way: Cyrillic input can be translated internally while the visible conversation remains natural. +- A right sidebar that shows what is happening under the hood: model status, context pressure, token usage, runtime memory, and live logs are there when you want them. +- A keyboard-first writing flow: Enter sends, Ctrl/Shift+Enter adds a newline, and the input box turns into the stop control while JIN is working. +- A local-first setup for people who run their own models: use separate brain, service, and translator runtimes, or collapse to one service model when you want a simpler setup. +- A deploy-friendly configuration story: use a local `config.py` while experimenting, then switch to environment variables when you are ready to run it somewhere more serious. ## Architecture @@ -54,7 +38,7 @@ Browser UI v FastAPI app.py | - +-- GET / -> templates/index.html + +-- GET / -> ui/templates/index.html +-- GET /api/status -> provider availability and runtime metadata +-- WS /ws/chat -> streaming chat transport | @@ -127,18 +111,15 @@ This gives JIN observable short-term memory and behavior adaptation without intr |-- websocket.py # WebSocket runtime loop and cancellation |-- websocket_logger.py # JSON logs for the UI console |-- config.example.py # Runtime configuration template +|-- config_loader.py # Local config module loader +|-- app_settings.py # Typed settings wrapper |-- package.json # Local command shortcuts |-- requirements.txt # Pinned Python dependencies |-- .github/workflows/ # GitHub Actions CI -|-- agents/ # Agent runtime and nodes +|-- agent/ # Agent runtime, state, router, and nodes |-- clients/ # Runtime client builders and provider helpers -|-- contracts/ # Runtime context contracts -|-- emitter/ # WebSocket JSON emitter -|-- memory/ # Memory and runtime state abstractions -|-- runtime/ # Runtime client, context, stream, registry -|-- settings/ # Config loader and typed settings wrapper -|-- static/ # Browser JavaScript and README assets -|-- templates/ # HTML UI +|-- runtime/ # Runtime client, context, contracts, memory, stream, registry +|-- ui/ # HTML templates, browser JavaScript, and README assets |-- tests/ # Unit and optional model integration tests `-- utils/ # Stream, telemetry, language, token, error helpers ``` @@ -206,6 +187,17 @@ http://127.0.0.1:8000 `config.py` defines model providers, model IDs, request limits, context windows, and generation parameters. It is intentionally ignored by Git because it contains local runtime addresses. When `config.py` is absent, the app falls back to `config.example.py`, which keeps CI and basic tests runnable without private local settings. +For deployment, every uppercase option can also be provided through environment variables. Environment values override `config.py` and `config.example.py`. Both plain names and `JIN_`-prefixed names are supported: + +```bash +BRAIN_API_BASE=http://brain-host:1234 +JIN_SERVICE_MODEL_UID=service-model +USE_SERVICE_AS_BRAIN=true +SEARCH_TIMEOUT=20.0 +``` + +Plain names take priority over prefixed names when both are set. Boolean env values accept `1`, `true`, `yes`, `on`, `0`, `false`, `no`, and `off`. + ```python USE_SERVICE_AS_BRAIN = False @@ -354,12 +346,12 @@ Runtime memory update: The UI is served directly by FastAPI: -- `templates/index.html` renders the shell. -- `static/socket.js` handles WebSocket connection, send, abort, and stream events. -- `static/chat.js` renders normal and streaming messages. -- `static/status.js` updates provider online/offline indicators. -- `static/telemetry.js` updates runtime status, context usage, runtime memory snapshots, and memory diff highlighting. -- `static/logger.js` renders the runtime console. -- `static/dragdrop.js` handles attachment UI state. +- `ui/templates/index.html` renders the shell. +- `ui/static/js/socket.js` handles WebSocket connection, send, abort, and stream events. +- `ui/static/js/chat.js` renders normal and streaming messages. +- `ui/static/js/status.js` updates provider online/offline indicators. +- `ui/static/js/telemetry.js` updates runtime status, context usage, runtime memory snapshots, and memory diff highlighting. +- `ui/static/js/logger.js` renders the runtime console. +- `ui/static/js/dragdrop.js` handles attachment UI state. The frontend uses vanilla JavaScript and Tailwind from CDN. The current input behavior is keyboard-first: Enter sends, Ctrl/Shift+Enter inserts a newline, and the whole input field becomes a red stop control while a generation is active. diff --git a/agent/__init__.py b/agent/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/agent/nodes/__init__.py b/agent/nodes/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/agent/nodes/__init__.py @@ -0,0 +1 @@ + diff --git a/agents/base_node.py b/agent/nodes/base.py similarity index 100% rename from agents/base_node.py rename to agent/nodes/base.py diff --git a/agents/brain_node.py b/agent/nodes/brain.py similarity index 98% rename from agents/brain_node.py rename to agent/nodes/brain.py index fd804c3d..98477d52 100644 --- a/agents/brain_node.py +++ b/agent/nodes/brain.py @@ -1,6 +1,6 @@ -from agents.base_node import BaseNode +from agent.nodes.base import BaseNode -from runtime.runtime_stream import ( +from runtime.stream import ( RuntimeStream, ) diff --git a/agents/planner_node.py b/agent/nodes/planner.py similarity index 92% rename from agents/planner_node.py rename to agent/nodes/planner.py index e3c3adaa..a98f8a8b 100644 --- a/agents/planner_node.py +++ b/agent/nodes/planner.py @@ -1,9 +1,9 @@ -from agents.base_node import BaseNode +from agent.nodes.base import BaseNode from utils.language import ( contains_cyrillic, ) -from settings.config_loader import ( +from config_loader import ( config, ) diff --git a/agents/translation_node.py b/agent/nodes/translation.py similarity index 95% rename from agents/translation_node.py rename to agent/nodes/translation.py index b8ead6a9..ced480df 100644 --- a/agents/translation_node.py +++ b/agent/nodes/translation.py @@ -1,10 +1,10 @@ -from agents.base_node import BaseNode +from agent.nodes.base import BaseNode from clients.translation_client import ( translate, ) -from settings.app_settings import ( +from app_settings import ( settings, ) diff --git a/agents/validation_node.py b/agent/nodes/validation.py similarity index 95% rename from agents/validation_node.py rename to agent/nodes/validation.py index 947b11a6..02d3bc3d 100644 --- a/agents/validation_node.py +++ b/agent/nodes/validation.py @@ -1,10 +1,10 @@ -from agents.base_node import BaseNode +from agent.nodes.base import BaseNode -from runtime.runtime_stream import ( +from runtime.stream import ( RuntimeStream, ) -from settings.app_settings import ( +from app_settings import ( settings, ) diff --git a/agents/router.py b/agent/router.py similarity index 100% rename from agents/router.py rename to agent/router.py diff --git a/agents/agent_runtime.py b/agent/runtime.py similarity index 83% rename from agents/agent_runtime.py rename to agent/runtime.py index 2edc7cb1..891cb3b7 100644 --- a/agents/agent_runtime.py +++ b/agent/runtime.py @@ -1,20 +1,20 @@ -from agents.planner_node import ( +from agent.nodes.planner import ( PlannerNode, ) -from agents.translation_node import ( +from agent.nodes.translation import ( TranslationNode, ) -from agents.validation_node import ( +from agent.nodes.validation import ( ValidationNode, ) -from agents.router import ( +from agent.router import ( Router, ) -from agents.brain_node import ( +from agent.nodes.brain import ( BrainNode, ) diff --git a/agents/agent_state.py b/agent/state.py similarity index 100% rename from agents/agent_state.py rename to agent/state.py diff --git a/app.py b/app.py index 735302f4..bd60626c 100644 --- a/app.py +++ b/app.py @@ -20,7 +20,7 @@ import asyncio import httpx -from settings.config_loader import ( +from config_loader import ( config, ) @@ -32,11 +32,11 @@ websocket_router, ) -from clients.clients_registry import ( +from clients.registry import ( build_clients, ) -from memory.runtime_state import ( +from runtime.state import ( RUNTIME_MEMORY_SUMMARIZER_LABEL, ) @@ -92,12 +92,12 @@ async def lifespan(application: FastAPI): ) templates = Jinja2Templates( - directory="templates", + directory="ui/templates", ) app.mount( "/static", - StaticFiles(directory="static"), + StaticFiles(directory="ui/static"), name="static", ) diff --git a/settings/app_settings.py b/app_settings.py similarity index 98% rename from settings/app_settings.py rename to app_settings.py index be4d5e12..9563128b 100644 --- a/settings/app_settings.py +++ b/app_settings.py @@ -1,6 +1,6 @@ from dataclasses import dataclass -from settings.config_loader import ( +from config_loader import ( config, ) diff --git a/clients/__init__.py b/clients/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/clients/brain_client.py b/clients/brain_client.py index 73ef34f2..b57a78d0 100644 --- a/clients/brain_client.py +++ b/clients/brain_client.py @@ -3,10 +3,10 @@ from xml.etree import ElementTree from xml.sax.saxutils import escape -from settings.config_loader import ( +from config_loader import ( config, ) -from contracts.context_contract import ( +from runtime.context_contract import ( ContextContract, DEEP_THOUGHT_ACTION, RUNTIME_ACTION_DEEP_THOUGHT, @@ -14,7 +14,7 @@ SEARCH_ACTION_TEMPLATE, ) -from utils.errors import ( +from clients.errors import ( format_client_error, ) @@ -23,7 +23,7 @@ ask_service_model_stream, ) -from utils.response_extractor import ( +from clients.response_extractor import ( ResponseExtractor, ) diff --git a/utils/errors.py b/clients/errors.py similarity index 100% rename from utils/errors.py rename to clients/errors.py diff --git a/clients/clients_registry.py b/clients/registry.py similarity index 94% rename from clients/clients_registry.py rename to clients/registry.py index 5bcef9fe..6e9ec00d 100644 --- a/clients/clients_registry.py +++ b/clients/registry.py @@ -1,6 +1,6 @@ -from settings.app_settings import settings +from app_settings import settings -from runtime.runtime_client import ( +from runtime.client import ( RuntimeClient, ) diff --git a/utils/response_extractor.py b/clients/response_extractor.py similarity index 100% rename from utils/response_extractor.py rename to clients/response_extractor.py diff --git a/clients/search_client.py b/clients/search_client.py index b6a97a87..d5a5589f 100644 --- a/clients/search_client.py +++ b/clients/search_client.py @@ -3,11 +3,11 @@ import httpx -from settings.app_settings import ( +from app_settings import ( settings, ) -from utils.search import ( +from clients.search_provider import ( serper_search, ) diff --git a/utils/search.py b/clients/search_provider.py similarity index 100% rename from utils/search.py rename to clients/search_provider.py diff --git a/clients/translation_client.py b/clients/translation_client.py index 1b07e579..0cc7dbd5 100644 --- a/clients/translation_client.py +++ b/clients/translation_client.py @@ -1,9 +1,9 @@ import asyncio -from settings.config_loader import ( +from config_loader import ( config, ) -from utils.errors import ( +from clients.errors import ( format_client_error, ) @@ -11,7 +11,7 @@ translation_token_limit, ) -from utils.response_extractor import ( +from clients.response_extractor import ( ResponseExtractor, ) diff --git a/config_loader.py b/config_loader.py new file mode 100644 index 00000000..708b0845 --- /dev/null +++ b/config_loader.py @@ -0,0 +1,176 @@ +from importlib import import_module +from importlib import util +import os +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent +ENV_PREFIX = "JIN_" + + +def parse_env_value( + *, + name: str, + raw_value: str, + current_value, +): + + if isinstance(current_value, bool): + + normalized = ( + raw_value + .strip() + .lower() + ) + + if normalized in { + "1", + "true", + "yes", + "on", + }: + return True + + if normalized in { + "0", + "false", + "no", + "off", + }: + return False + + raise ValueError( + f"Invalid boolean env value for {name}: {raw_value!r}" + ) + + if isinstance(current_value, int): + return int( + raw_value + ) + + if isinstance(current_value, float): + return float( + raw_value + ) + + return raw_value + + +def get_env_override( + name: str, +) -> str | None: + + if name in os.environ: + return os.environ[name] + + prefixed_name = f"{ENV_PREFIX}{name}" + + return os.environ.get( + prefixed_name + ) + + +def apply_env_overrides( + config_module, +): + + for name in dir(config_module): + + if not name.isupper(): + continue + + raw_value = get_env_override( + name + ) + + if raw_value is None: + continue + + current_value = getattr( + config_module, + name, + ) + + setattr( + config_module, + name, + parse_env_value( + name=name, + raw_value=raw_value, + current_value=current_value, + ), + ) + + return config_module + + +def load_config_from_path( + path: Path, +): + + spec = util.spec_from_file_location( + path.stem, + path, + ) + + if ( + spec is None + or spec.loader is None + ): + raise RuntimeError( + f"Unable to load config from {path}" + ) + + module = util.module_from_spec( + spec + ) + + spec.loader.exec_module( + module + ) + + return module + + +def load_config_module( + *, + config_path: Path | None = None, + example_path: Path | None = None, +): + + if config_path is None: + + try: + return apply_env_overrides( + import_module( + "config" + ) + ) + + except ModuleNotFoundError as error: + + if error.name != "config": + raise + + else: + + if config_path.exists(): + return apply_env_overrides( + load_config_from_path( + config_path + ) + ) + + fallback_path = ( + example_path + or ROOT / "config.example.py" + ) + + return apply_env_overrides( + load_config_from_path( + fallback_path + ) + ) + + +config = load_config_module() diff --git a/emitter/runtime_emitter.py b/emitter/runtime_emitter.py deleted file mode 100644 index b7f6a958..00000000 --- a/emitter/runtime_emitter.py +++ /dev/null @@ -1,17 +0,0 @@ -class RuntimeEmitter: - - def __init__( - self, - websocket, - ): - - self.websocket = websocket - - async def emit( - self, - payload: dict, - ): - - await self.websocket.send_json( - payload - ) \ No newline at end of file diff --git a/package.json b/package.json index c26b4840..28b071ec 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,6 @@ "scripts": { "test": "python -m unittest discover -s tests", "tests": "npm test", - "translation_tests": "python tools/run_translation_tests.py" + "translation_tests": "python tests/run_translation_tests.py" } } diff --git a/runtime/__init__.py b/runtime/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/runtime/runtime_client.py b/runtime/client.py similarity index 99% rename from runtime/runtime_client.py rename to runtime/client.py index e1799833..7a21b02a 100644 --- a/runtime/runtime_client.py +++ b/runtime/client.py @@ -4,13 +4,13 @@ import httpx -from settings.app_settings import settings +from app_settings import settings from utils.urls import ( join_url, ) -from utils.response_extractor import ( +from clients.response_extractor import ( ResponseExtractor, ) diff --git a/runtime/runtime_context.py b/runtime/context.py similarity index 85% rename from runtime/runtime_context.py rename to runtime/context.py index f12909cf..c8e07bf3 100644 --- a/runtime/runtime_context.py +++ b/runtime/context.py @@ -2,15 +2,30 @@ from websocket_logger import WebSocketLogger -from emitter.runtime_emitter import ( - RuntimeEmitter, -) - -from memory.message_memory import ( +from runtime.memory import ( DEFAULT_RUNTIME_MEMORY, ) +class RuntimeEmitter: + + def __init__( + self, + websocket, + ): + + self.websocket = websocket + + async def emit( + self, + payload: dict, + ): + + await self.websocket.send_json( + payload + ) + + @dataclass class RuntimeContext: diff --git a/contracts/context_contract.py b/runtime/context_contract.py similarity index 100% rename from contracts/context_contract.py rename to runtime/context_contract.py diff --git a/memory/message_memory.py b/runtime/memory.py similarity index 99% rename from memory/message_memory.py rename to runtime/memory.py index b74eb703..5b73b183 100644 --- a/memory/message_memory.py +++ b/runtime/memory.py @@ -7,16 +7,16 @@ from clients.service_client import ( ask_service_model, ) -from memory.runtime_state import ( +from runtime.state import ( RUNTIME_MEMORY_SUMMARIZER_RUNTIME_ID, ) -from settings.config_loader import ( +from config_loader import ( config, ) -from utils.response_extractor import ( +from clients.response_extractor import ( ResponseExtractor, ) -from utils.runtime_state_sync import ( +from runtime.state_sync import ( refresh_runtime_state, ) from utils.tokens import ( diff --git a/runtime/registry.py b/runtime/registry.py new file mode 100644 index 00000000..be109f7d --- /dev/null +++ b/runtime/registry.py @@ -0,0 +1,3 @@ +from runtime.state import RuntimeState + +runtime_state = RuntimeState() diff --git a/runtime/runtime_registry.py b/runtime/runtime_registry.py deleted file mode 100644 index d549e06f..00000000 --- a/runtime/runtime_registry.py +++ /dev/null @@ -1,3 +0,0 @@ -from memory.runtime_state import RuntimeState - -runtime_state = RuntimeState() \ No newline at end of file diff --git a/memory/runtime_state.py b/runtime/state.py similarity index 98% rename from memory/runtime_state.py rename to runtime/state.py index aa54ec94..fbb0ae11 100644 --- a/memory/runtime_state.py +++ b/runtime/state.py @@ -1,4 +1,4 @@ -from settings.app_settings import settings +from app_settings import settings UNCHANGED = object() diff --git a/utils/runtime_state_sync.py b/runtime/state_sync.py similarity index 90% rename from utils/runtime_state_sync.py rename to runtime/state_sync.py index 004402e4..45de1f8a 100644 --- a/utils/runtime_state_sync.py +++ b/runtime/state_sync.py @@ -1,12 +1,12 @@ -from memory.runtime_state import ( +from runtime.state import ( UNCHANGED, ) -from runtime.runtime_registry import ( +from runtime.registry import ( runtime_state, ) -from utils.telemetry import ( +from runtime.telemetry import ( send_telemetry, ) diff --git a/runtime/runtime_stream.py b/runtime/stream.py similarity index 99% rename from runtime/runtime_stream.py rename to runtime/stream.py index 648196c4..375bf443 100644 --- a/runtime/runtime_stream.py +++ b/runtime/stream.py @@ -4,7 +4,7 @@ import httpx -from utils.runtime_state_sync import ( +from runtime.state_sync import ( refresh_runtime_state, ) diff --git a/utils/telemetry.py b/runtime/telemetry.py similarity index 85% rename from utils/telemetry.py rename to runtime/telemetry.py index 3ed3ffa0..1b3273d4 100644 --- a/utils/telemetry.py +++ b/runtime/telemetry.py @@ -1,4 +1,4 @@ -from runtime.runtime_registry import ( +from runtime.registry import ( runtime_state, ) diff --git a/settings/config_loader.py b/settings/config_loader.py deleted file mode 100644 index 9dda54a0..00000000 --- a/settings/config_loader.py +++ /dev/null @@ -1,72 +0,0 @@ -from importlib import import_module -from importlib import util -from pathlib import Path - - -ROOT = Path(__file__).resolve().parents[1] - - -def load_config_from_path( - path: Path, -): - - spec = util.spec_from_file_location( - path.stem, - path, - ) - - if ( - spec is None - or spec.loader is None - ): - raise RuntimeError( - f"Unable to load config from {path}" - ) - - module = util.module_from_spec( - spec - ) - - spec.loader.exec_module( - module - ) - - return module - - -def load_config_module( - *, - config_path: Path | None = None, - example_path: Path | None = None, -): - - if config_path is None: - - try: - return import_module( - "config" - ) - - except ModuleNotFoundError as error: - - if error.name != "config": - raise - - else: - - if config_path.exists(): - return load_config_from_path( - config_path - ) - - fallback_path = ( - example_path - or ROOT / "config.example.py" - ) - - return load_config_from_path( - fallback_path - ) - - -config = load_config_module() diff --git a/static/images/highlight_v0.1.png b/static/images/highlight_v0.1.png deleted file mode 100644 index 4e163303f42534987567881a1eeddc1696f3b61a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64675 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 z 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 "runtimeConfig": runtime_config, } | tojson }} - - - - - - + + + + + + diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/utils/brain.py b/utils/brain.py index 39e7e6de..14457f2b 100644 --- a/utils/brain.py +++ b/utils/brain.py @@ -1,4 +1,4 @@ -from settings.app_settings import settings +from app_settings import settings SERVICE_AS_BRAIN_RUNTIME_ACTIONS = { diff --git a/utils/runtime_actions.py b/utils/runtime_actions.py index 84a6ab49..1acfc07a 100644 --- a/utils/runtime_actions.py +++ b/utils/runtime_actions.py @@ -2,7 +2,7 @@ import re from dataclasses import dataclass -from contracts.context_contract import ( +from runtime.context_contract import ( DEEP_THOUGHT_ACTION, RUNTIME_ACTION_DEEP_THOUGHT, RUNTIME_ACTION_SEARCH, diff --git a/utils/tokens.py b/utils/tokens.py index 15607de0..a3364aa0 100644 --- a/utils/tokens.py +++ b/utils/tokens.py @@ -1,4 +1,4 @@ -from settings.app_settings import settings +from app_settings import settings def estimate_tokens( diff --git a/utils/ws_errors.py b/utils/ws_errors.py index d0d27933..1ddad41c 100644 --- a/utils/ws_errors.py +++ b/utils/ws_errors.py @@ -1,7 +1,7 @@ import traceback import logging -from utils.runtime_state_sync import ( +from runtime.state_sync import ( set_runtime_offline, ) diff --git a/websocket.py b/websocket.py index c35f841d..86a15156 100644 --- a/websocket.py +++ b/websocket.py @@ -15,7 +15,7 @@ WebSocketLogger, ) -from agents.agent_runtime import ( +from agent.runtime import ( AgentRuntime, ) @@ -24,11 +24,11 @@ build_brain_system_prompt, ) -from agents.agent_state import ( +from agent.state import ( AgentState, ) -from memory.message_memory import ( +from runtime.memory import ( schedule_interrupted_runtime_memory_update, schedule_runtime_memory_update, cancel_runtime_memory_update, @@ -42,7 +42,7 @@ contains_cyrillic, ) -from utils.runtime_state_sync import ( +from runtime.state_sync import ( refresh_runtime_state, ) @@ -50,7 +50,7 @@ format_token_usage_summary, ) -from utils.telemetry import ( +from runtime.telemetry import ( send_telemetry, ) @@ -63,15 +63,12 @@ handle_websocket_error, ) -from runtime.runtime_context import ( +from runtime.context import ( RuntimeContext, -) - -from emitter.runtime_emitter import ( RuntimeEmitter, ) -from memory.message_memory import ( +from runtime.memory import ( build_runtime_memory_snapshot, )