codex-agent-framework 0.1.15__tar.gz → 0.1.17__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.
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/CHANGELOG.md +20 -0
- {codex_agent_framework-0.1.15/codex_agent_framework.egg-info → codex_agent_framework-0.1.17}/PKG-INFO +5 -1
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/README.md +4 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/agent.py +5 -3
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/cli/main.py +1 -1
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/config.py +8 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/mainloop.py +1 -1
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/message.py +0 -1
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/plugin.py +2 -0
- codex_agent_framework-0.1.17/codex_agent/server/__init__.py +29 -0
- codex_agent_framework-0.1.17/codex_agent/server/app.py +201 -0
- codex_agent_framework-0.1.15/codex_agent/server.py → codex_agent_framework-0.1.17/codex_agent/server/core.py +29 -208
- codex_agent_framework-0.1.17/codex_agent/tui/__init__.py +25 -0
- {codex_agent_framework-0.1.15/codex_agent → codex_agent_framework-0.1.17/codex_agent/tui}/chat.py +3 -3
- codex_agent_framework-0.1.15/codex_agent/tui.py → codex_agent_framework-0.1.17/codex_agent/tui/lifecycle.py +1 -1
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17/codex_agent_framework.egg-info}/PKG-INFO +5 -1
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent_framework.egg-info/SOURCES.txt +6 -3
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/pyproject.toml +1 -1
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_agent.py +23 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_chat.py +2 -2
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_server.py +21 -21
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_tui.py +1 -1
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/LICENSE +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/MANIFEST.in +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/__init__.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/__main__.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/agent_runtime.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/ai.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/builtin_commands.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/builtin_plugins/__init__.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/builtin_plugins/browser/__init__.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/builtin_plugins/browser/controller.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/builtin_plugins/desktop/__init__.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/builtin_plugins/desktop/controller.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/builtin_plugins/desktop/screenshot.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/builtin_plugins/memory/__init__.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/builtin_plugins/planner/__init__.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/builtin_plugins/scheduler/__init__.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/builtin_providers.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/builtin_tools/__init__.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/builtin_tools/files.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/builtin_tools/server_tools.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/builtin_tools/shell.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/builtin_tools/system.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/builtin_tools/vision.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/cli/__init__.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/cli/headless.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/cli/output.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/cli/root.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/cli/runner.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/client.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/command.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/context.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/event.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/get_text/__init__.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/get_text/default_gitignore +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/get_text/get_text.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/get_text/simpler_get_text.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/hooks.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/image.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/latex.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/memory.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/prompts/image_generation_system_prompt.txt +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/prompts/system_prompt.txt +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/provider.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/registry.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/runtime.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/scheduler.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/scripts/install-system-dependencies.sh +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/service.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/sessions.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/status.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/stream_utils.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/tool.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/tray.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/utils.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/voice.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent/worker.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent_framework.egg-info/dependency_links.txt +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent_framework.egg-info/entry_points.txt +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent_framework.egg-info/requires.txt +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/codex_agent_framework.egg-info/top_level.txt +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/dependencies.txt +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/setup.cfg +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_agent_runtime.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_ai.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_browser.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_builtin_config.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_cli.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_cli_headless.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_cli_root.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_client.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_events.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_get_text_browser.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_hooks.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_image_message.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_local_desktop.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_memory.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_messages.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_planner.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_scheduler.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_service.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_tray.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_utils.py +0 -0
- {codex_agent_framework-0.1.15 → codex_agent_framework-0.1.17}/tests/test_worker.py +0 -0
|
@@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
This project loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and uses semantic versioning where practical.
|
|
6
6
|
|
|
7
|
+
## [0.1.17] - 2026-05-08
|
|
8
|
+
### Changed
|
|
9
|
+
- Reorganize the Textual TUI into the `codex_agent.tui` package, splitting chat UI code from TUI lifecycle helpers.
|
|
10
|
+
- Reorganize the FastAPI bridge into the `codex_agent.server` package, separating server core/service logic from app and route creation.
|
|
11
|
+
- Remove the obsolete `Message.lasting` field now that message visibility is controlled by `turns_left`, reducing serialized payload noise.
|
|
12
|
+
|
|
13
|
+
### Removed
|
|
14
|
+
- Remove the old flat `codex_agent.chat` and `codex_agent.tui` modules in favor of the new package layout.
|
|
15
|
+
|
|
16
|
+
### Tests
|
|
17
|
+
- Validate the full suite at 442 passing tests.
|
|
18
|
+
|
|
19
|
+
## [0.1.16] - 2026-05-08
|
|
20
|
+
### Fixed
|
|
21
|
+
- Allow agent startup without an OpenAI API key by disabling OpenAI-dependent capabilities such as voice defaults and the built-in memory plugin instead of failing during initialization.
|
|
22
|
+
- Skip automatic memory archiving when the memory plugin is unavailable.
|
|
23
|
+
|
|
24
|
+
### Tests
|
|
25
|
+
- Add regression coverage for agent initialization with and without an OpenAI API key.
|
|
26
|
+
|
|
7
27
|
## [0.1.15] - 2026-05-08
|
|
8
28
|
### Added
|
|
9
29
|
- Add `AgentRuntime` as the canonical external runtime contract for HTTP server, headless CLI, in-process, and process-backed integrations.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-agent-framework
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.17
|
|
4
4
|
Summary: A lightweight event-driven Codex agent runtime.
|
|
5
5
|
Author: Baptiste
|
|
6
6
|
License-Expression: MIT
|
|
@@ -393,6 +393,8 @@ agent = Agent(
|
|
|
393
393
|
|
|
394
394
|
Configuration is persisted to `agent_config.json` in the runtime directory when updated through agent helpers, slash commands, the HTTP API, or `codex-agent config set`.
|
|
395
395
|
|
|
396
|
+
If no OpenAI API key is configured through `openai_api_key` or `OPENAI_API_KEY`, the agent can still start, but OpenAI-dependent capabilities such as voice defaults and the built-in memory plugin are disabled.
|
|
397
|
+
|
|
396
398
|
## Project layout
|
|
397
399
|
|
|
398
400
|
```text
|
|
@@ -440,6 +442,8 @@ python -m build
|
|
|
440
442
|
The distribution includes prompt text files, `codex_agent/get_text/default_gitignore`, and the packaged Linux system dependency installer through package data and `MANIFEST.in`.
|
|
441
443
|
|
|
442
444
|
## Recent changes
|
|
445
|
+
- `0.1.17`: reorganize the TUI and FastAPI server into dedicated `codex_agent.tui` and `codex_agent.server` packages, remove obsolete message payload fields, and clean unused imports.
|
|
446
|
+
- `0.1.16`: tolerate missing OpenAI API keys at startup by disabling OpenAI-dependent voice and memory features, and skip memory archiving when the memory plugin is unavailable.
|
|
443
447
|
- `0.1.15`: add the `AgentRuntime` interface for server/CLI/process adapters, split the CLI into a package with headless runtime commands, add `codex-agent config set` plus `PATCH /config`, and make built-in tools/providers/plugins configurable.
|
|
444
448
|
- `0.1.14`: add HookManager infrastructure, planner/scheduler robustness fixes, documented system dependencies, `codex-agent install-system-deps`, `codex-agent bootstrap`, and improved TUI SSE reconnect/replay handling.
|
|
445
449
|
- `0.1.13`: reorganize built-in tools into a `codex_agent.builtin_tools` package, keeping the public import surface compatible while separating file, shell, vision, system, and server-tool modules.
|
|
@@ -344,6 +344,8 @@ agent = Agent(
|
|
|
344
344
|
|
|
345
345
|
Configuration is persisted to `agent_config.json` in the runtime directory when updated through agent helpers, slash commands, the HTTP API, or `codex-agent config set`.
|
|
346
346
|
|
|
347
|
+
If no OpenAI API key is configured through `openai_api_key` or `OPENAI_API_KEY`, the agent can still start, but OpenAI-dependent capabilities such as voice defaults and the built-in memory plugin are disabled.
|
|
348
|
+
|
|
347
349
|
## Project layout
|
|
348
350
|
|
|
349
351
|
```text
|
|
@@ -391,6 +393,8 @@ python -m build
|
|
|
391
393
|
The distribution includes prompt text files, `codex_agent/get_text/default_gitignore`, and the packaged Linux system dependency installer through package data and `MANIFEST.in`.
|
|
392
394
|
|
|
393
395
|
## Recent changes
|
|
396
|
+
- `0.1.17`: reorganize the TUI and FastAPI server into dedicated `codex_agent.tui` and `codex_agent.server` packages, remove obsolete message payload fields, and clean unused imports.
|
|
397
|
+
- `0.1.16`: tolerate missing OpenAI API keys at startup by disabling OpenAI-dependent voice and memory features, and skip memory archiving when the memory plugin is unavailable.
|
|
394
398
|
- `0.1.15`: add the `AgentRuntime` interface for server/CLI/process adapters, split the CLI into a package with headless runtime commands, add `codex-agent config set` plus `PATCH /config`, and make built-in tools/providers/plugins configurable.
|
|
395
399
|
- `0.1.14`: add HookManager infrastructure, planner/scheduler robustness fixes, documented system dependencies, `codex-agent install-system-deps`, `codex-agent bootstrap`, and improved TUI SSE reconnect/replay handling.
|
|
396
400
|
- `0.1.13`: reorganize built-in tools into a `codex_agent.builtin_tools` package, keeping the public import surface compatible while separating file, shell, vision, system, and server-tool modules.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
from .utils import text_content, ensure_runtime_dir, upload_file_to_folder, project_python_env
|
|
2
|
-
from modict import modict
|
|
1
|
+
from .utils import text_content, ensure_runtime_dir, upload_file_to_folder, project_python_env, runtime_join
|
|
3
2
|
import os
|
|
4
3
|
import sys
|
|
5
4
|
from .message import DeveloperMessage, ServerToolResponseMessage, SystemMessage, ToolResultMessage, UserMessage, message_from_data
|
|
@@ -17,6 +16,7 @@ from .status import StatusManager
|
|
|
17
16
|
from .tool import ImageGenerationTool, reset_current_agent, set_current_agent
|
|
18
17
|
from .ai import AIBadRequestError, AIClient
|
|
19
18
|
from .voice import VoiceProcessor
|
|
19
|
+
from .memory import RAGMemory
|
|
20
20
|
from .latex import LaTeXProcessor
|
|
21
21
|
from .event import AgentInterruptRequestedEvent, AgentInterrupted, AgentInterruptedEvent, AudioPlaybackEvent, EventBus
|
|
22
22
|
from pynteract import Shell
|
|
@@ -39,6 +39,7 @@ class Agent:
|
|
|
39
39
|
self.config=self.config_manager.config
|
|
40
40
|
self.config_manager.load()
|
|
41
41
|
self.config_manager.update(kwargs, save=False)
|
|
42
|
+
self.config_manager.apply_openai_capability_defaults()
|
|
42
43
|
self.events=EventBus()
|
|
43
44
|
self.registry=RegistryManager(self)
|
|
44
45
|
self.tools=self.registry.tools
|
|
@@ -65,6 +66,7 @@ class Agent:
|
|
|
65
66
|
self.registry.init_providers_folder()
|
|
66
67
|
self.command_manager.init_folder()
|
|
67
68
|
self.ai=AIClient(self)
|
|
69
|
+
self.memory=RAGMemory(file=runtime_join("memory.json"))
|
|
68
70
|
self.voice=VoiceProcessor(self)
|
|
69
71
|
self.latex=LaTeXProcessor()
|
|
70
72
|
self.content_processors=[self.voice]
|
|
@@ -429,5 +431,5 @@ class Agent:
|
|
|
429
431
|
import os
|
|
430
432
|
if os.environ.get('AGENT_ROOT_PATH') is None:
|
|
431
433
|
set_root_path(file=__file__)
|
|
432
|
-
from .
|
|
434
|
+
from .tui import Chat
|
|
433
435
|
Chat(self).run()
|
|
@@ -2,7 +2,7 @@ from pathlib import Path
|
|
|
2
2
|
import subprocess
|
|
3
3
|
|
|
4
4
|
from codex_agent import Agent
|
|
5
|
-
from codex_agent.
|
|
5
|
+
from codex_agent.tui import Chat
|
|
6
6
|
from codex_agent.server import create_app
|
|
7
7
|
from codex_agent.service import install_user_service, uninstall_user_service
|
|
8
8
|
from codex_agent.tray import run_tray
|
|
@@ -121,6 +121,14 @@ class ConfigManager:
|
|
|
121
121
|
self.save()
|
|
122
122
|
return self.config
|
|
123
123
|
|
|
124
|
+
def has_openai_api_key(self):
|
|
125
|
+
return bool(self.config.get("openai_api_key") or os.getenv("OPENAI_API_KEY"))
|
|
126
|
+
|
|
127
|
+
def apply_openai_capability_defaults(self):
|
|
128
|
+
if not self.has_openai_api_key():
|
|
129
|
+
self.config.voice_enabled = False
|
|
130
|
+
return self.config
|
|
131
|
+
|
|
124
132
|
def system_message(self):
|
|
125
133
|
return SystemMessage(content=self.system_prompt())
|
|
126
134
|
|
|
@@ -265,7 +265,7 @@ class MainLoopManager:
|
|
|
265
265
|
|
|
266
266
|
if self.require_next_step and not self.stop_agentic_loop:
|
|
267
267
|
continue
|
|
268
|
-
if self.agent.config.get("auto_archive_memory", True):
|
|
268
|
+
if self.agent.config.get("auto_archive_memory", True) and self.agent.plugins.get("memory"):
|
|
269
269
|
self.agent.plugins.memory.archive_last_turn_async()
|
|
270
270
|
return True
|
|
271
271
|
except AgentInterrupted:
|
|
@@ -62,6 +62,8 @@ class PluginsManager:
|
|
|
62
62
|
continue
|
|
63
63
|
if not self.agent.config_manager.builtin_enabled("plugins", module_info.name):
|
|
64
64
|
continue
|
|
65
|
+
if module_info.name == "memory" and not self.agent.config_manager.has_openai_api_key():
|
|
66
|
+
continue
|
|
65
67
|
module = importlib.import_module(f"{builtin_plugins.__name__}.{module_info.name}")
|
|
66
68
|
self.add_builtin_from_module(module)
|
|
67
69
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""FastAPI server package for codex-agent."""
|
|
2
|
+
|
|
3
|
+
from .app import app, create_app, main
|
|
4
|
+
from .core import (
|
|
5
|
+
AgentServer,
|
|
6
|
+
SERVER_CONFIG_FILE,
|
|
7
|
+
SERVER_EVENTS_FILE,
|
|
8
|
+
create_server_agent,
|
|
9
|
+
load_server_config,
|
|
10
|
+
remembered_server_session,
|
|
11
|
+
save_server_config,
|
|
12
|
+
server_config_file,
|
|
13
|
+
server_events_file,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"AgentServer",
|
|
18
|
+
"SERVER_CONFIG_FILE",
|
|
19
|
+
"SERVER_EVENTS_FILE",
|
|
20
|
+
"app",
|
|
21
|
+
"create_app",
|
|
22
|
+
"create_server_agent",
|
|
23
|
+
"load_server_config",
|
|
24
|
+
"main",
|
|
25
|
+
"remembered_server_session",
|
|
26
|
+
"save_server_config",
|
|
27
|
+
"server_config_file",
|
|
28
|
+
"server_events_file",
|
|
29
|
+
]
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from ..agent import Agent
|
|
4
|
+
from ..agent_runtime import InProcessAgentRuntime, ProcessAgentRuntime
|
|
5
|
+
from .core import AgentServer, create_server_agent, remembered_server_session
|
|
6
|
+
|
|
7
|
+
def create_app(agent: Agent | None = None, start_agent=False, process_agent=False, **agent_kwargs):
|
|
8
|
+
from contextlib import asynccontextmanager
|
|
9
|
+
|
|
10
|
+
from fastapi import FastAPI, HTTPException, Request
|
|
11
|
+
from fastapi.responses import StreamingResponse
|
|
12
|
+
from pydantic import BaseModel
|
|
13
|
+
|
|
14
|
+
if process_agent and agent is None:
|
|
15
|
+
if "session" not in agent_kwargs:
|
|
16
|
+
agent_kwargs["session"] = remembered_server_session() or "latest"
|
|
17
|
+
runtime = ProcessAgentRuntime(agent_kwargs=agent_kwargs)
|
|
18
|
+
else:
|
|
19
|
+
runtime = InProcessAgentRuntime(agent or create_server_agent(**agent_kwargs))
|
|
20
|
+
service = AgentServer(runtime=runtime)
|
|
21
|
+
if start_agent:
|
|
22
|
+
service.start()
|
|
23
|
+
|
|
24
|
+
@asynccontextmanager
|
|
25
|
+
async def lifespan(app):
|
|
26
|
+
try:
|
|
27
|
+
yield
|
|
28
|
+
finally:
|
|
29
|
+
service.stop(timeout=1.0)
|
|
30
|
+
|
|
31
|
+
app = FastAPI(title="Codex Agent", version="0.1.17", lifespan=lifespan)
|
|
32
|
+
app.state.agent_service = service
|
|
33
|
+
|
|
34
|
+
class TurnRequest(BaseModel):
|
|
35
|
+
prompt: str
|
|
36
|
+
|
|
37
|
+
class InterruptRequest(BaseModel):
|
|
38
|
+
reason: str = "api"
|
|
39
|
+
|
|
40
|
+
class ConfigUpdateRequest(BaseModel):
|
|
41
|
+
values: dict = {}
|
|
42
|
+
save: bool = True
|
|
43
|
+
|
|
44
|
+
class WakeupRequest(BaseModel):
|
|
45
|
+
prompt: str
|
|
46
|
+
run_at: str | None = None
|
|
47
|
+
delay_seconds: float | None = None
|
|
48
|
+
interval_seconds: float | None = None
|
|
49
|
+
title: str = ""
|
|
50
|
+
|
|
51
|
+
class RestartRequest(BaseModel):
|
|
52
|
+
prompt: str | None = None
|
|
53
|
+
title: str = "post-restart wakeup"
|
|
54
|
+
|
|
55
|
+
def jsonable(value):
|
|
56
|
+
return json.loads(json.dumps(value, ensure_ascii=False, default=str))
|
|
57
|
+
|
|
58
|
+
def busy_guard(call):
|
|
59
|
+
try:
|
|
60
|
+
return call()
|
|
61
|
+
except RuntimeError as exc:
|
|
62
|
+
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
|
63
|
+
except ValueError as exc:
|
|
64
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
65
|
+
|
|
66
|
+
@app.get("/health")
|
|
67
|
+
def health():
|
|
68
|
+
return {"ok": True, "busy": service.busy, "session_id": service.current_session_id}
|
|
69
|
+
|
|
70
|
+
@app.get("/status")
|
|
71
|
+
def status():
|
|
72
|
+
return jsonable(service.status())
|
|
73
|
+
|
|
74
|
+
@app.get("/config")
|
|
75
|
+
def config():
|
|
76
|
+
return jsonable(service.config())
|
|
77
|
+
|
|
78
|
+
@app.patch("/config")
|
|
79
|
+
def update_config(request: ConfigUpdateRequest):
|
|
80
|
+
return jsonable(busy_guard(lambda: service.update_config(request.values, save=request.save)))
|
|
81
|
+
|
|
82
|
+
@app.get("/session")
|
|
83
|
+
def session():
|
|
84
|
+
return jsonable(service.session())
|
|
85
|
+
|
|
86
|
+
@app.get("/sessions")
|
|
87
|
+
def sessions():
|
|
88
|
+
return jsonable(service.sessions())
|
|
89
|
+
|
|
90
|
+
@app.post("/sessions/new")
|
|
91
|
+
def start_new_session():
|
|
92
|
+
return jsonable(busy_guard(service.start_new_session))
|
|
93
|
+
|
|
94
|
+
@app.post("/sessions/{session_id}/load")
|
|
95
|
+
def load_session(session_id: str):
|
|
96
|
+
return jsonable(busy_guard(lambda: service.load_session(session_id)))
|
|
97
|
+
|
|
98
|
+
@app.delete("/sessions/{session_id}")
|
|
99
|
+
def delete_session(session_id: str):
|
|
100
|
+
return jsonable(busy_guard(lambda: service.delete_session(session_id)))
|
|
101
|
+
|
|
102
|
+
@app.post("/sessions/navigate/{direction}")
|
|
103
|
+
def navigate_session(direction: str):
|
|
104
|
+
return jsonable(busy_guard(lambda: service.navigate_session(direction)))
|
|
105
|
+
|
|
106
|
+
@app.get("/messages")
|
|
107
|
+
def messages():
|
|
108
|
+
return jsonable(service.messages())
|
|
109
|
+
|
|
110
|
+
@app.get("/memory")
|
|
111
|
+
def memory():
|
|
112
|
+
return jsonable(service.memory())
|
|
113
|
+
|
|
114
|
+
@app.get("/tools")
|
|
115
|
+
def tools():
|
|
116
|
+
return jsonable(service.tools())
|
|
117
|
+
|
|
118
|
+
@app.get("/tui")
|
|
119
|
+
def tui():
|
|
120
|
+
return jsonable(service.tui())
|
|
121
|
+
|
|
122
|
+
@app.post("/tui/open")
|
|
123
|
+
def open_tui(request: Request):
|
|
124
|
+
base_url = str(request.base_url).rstrip("/")
|
|
125
|
+
return jsonable(busy_guard(lambda: service.open_tui(base_url=base_url)))
|
|
126
|
+
|
|
127
|
+
@app.post("/tui/close")
|
|
128
|
+
def close_tui():
|
|
129
|
+
return jsonable(service.close_tui())
|
|
130
|
+
|
|
131
|
+
@app.post("/restart")
|
|
132
|
+
@app.post("/restart/")
|
|
133
|
+
def restart(request: RestartRequest):
|
|
134
|
+
return jsonable(busy_guard(lambda: service.restart(**request.model_dump())))
|
|
135
|
+
|
|
136
|
+
@app.get("/wakeups")
|
|
137
|
+
def wakeups(include_done: bool = True):
|
|
138
|
+
return jsonable(service.wakeups(include_done=include_done))
|
|
139
|
+
|
|
140
|
+
@app.post("/wakeups", status_code=201)
|
|
141
|
+
def schedule_wakeup(request: WakeupRequest):
|
|
142
|
+
return jsonable(busy_guard(lambda: service.schedule_wakeup(**request.model_dump())))
|
|
143
|
+
|
|
144
|
+
@app.post("/wakeups/{job_id}/cancel")
|
|
145
|
+
def cancel_wakeup(job_id: str):
|
|
146
|
+
return jsonable(busy_guard(lambda: service.cancel_wakeup(job_id)))
|
|
147
|
+
|
|
148
|
+
@app.delete("/wakeups/{job_id}")
|
|
149
|
+
def delete_wakeup(job_id: str):
|
|
150
|
+
return jsonable(busy_guard(lambda: service.delete_wakeup(job_id)))
|
|
151
|
+
|
|
152
|
+
@app.post("/turns", status_code=202)
|
|
153
|
+
def turns(request: TurnRequest):
|
|
154
|
+
result = busy_guard(lambda: service.submit_prompt(request.prompt))
|
|
155
|
+
return {"accepted": True, **jsonable(result)}
|
|
156
|
+
|
|
157
|
+
@app.post("/interrupt")
|
|
158
|
+
def interrupt(request: InterruptRequest):
|
|
159
|
+
return jsonable(service.interrupt(request.reason))
|
|
160
|
+
|
|
161
|
+
@app.get("/events")
|
|
162
|
+
def events(limit: int | None = None, after_sequence: int | None = None, client_pid: int | None = None):
|
|
163
|
+
try:
|
|
164
|
+
subscription = service.subscribe(after_sequence=after_sequence, client_pid=client_pid)
|
|
165
|
+
except RuntimeError as exc:
|
|
166
|
+
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
|
167
|
+
|
|
168
|
+
def stream():
|
|
169
|
+
count = 0
|
|
170
|
+
for payload in subscription:
|
|
171
|
+
lines = []
|
|
172
|
+
if payload.get("id"):
|
|
173
|
+
lines.append(f"id: {payload.id}")
|
|
174
|
+
lines.append(f"event: {payload.get('type', 'message')}")
|
|
175
|
+
lines.append(f"data: {json.dumps(payload, ensure_ascii=False, default=str)}")
|
|
176
|
+
yield "\n".join(lines) + "\n\n"
|
|
177
|
+
count += 1
|
|
178
|
+
if limit is not None and count >= limit:
|
|
179
|
+
break
|
|
180
|
+
return StreamingResponse(stream(), media_type="text/event-stream")
|
|
181
|
+
|
|
182
|
+
@app.get("/events/replay")
|
|
183
|
+
def replay_events(before_sequence: int | None = None, session_id: str | None = None):
|
|
184
|
+
return jsonable(service.replay_events(before_sequence=before_sequence, session_id=session_id))
|
|
185
|
+
|
|
186
|
+
return app
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
app = None
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def main():
|
|
193
|
+
import uvicorn
|
|
194
|
+
|
|
195
|
+
global app
|
|
196
|
+
app = create_app(start_agent=True, process_agent=True)
|
|
197
|
+
uvicorn.run(app, host="127.0.0.1", port=8765, timeout_graceful_shutdown=2)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
if __name__ == "__main__":
|
|
201
|
+
main()
|
|
@@ -8,14 +8,14 @@ from collections import deque
|
|
|
8
8
|
|
|
9
9
|
from modict import modict
|
|
10
10
|
|
|
11
|
-
from
|
|
12
|
-
from
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
-
from
|
|
16
|
-
from
|
|
17
|
-
from
|
|
18
|
-
from
|
|
11
|
+
from ..agent import Agent
|
|
12
|
+
from ..agent_runtime import AgentRuntime
|
|
13
|
+
from ..event import Event
|
|
14
|
+
from ..message import UserMessage
|
|
15
|
+
from ..sessions import AgentSession
|
|
16
|
+
from ..service import pop_restart_wakeup, restart_server_service, write_restart_wakeup
|
|
17
|
+
from ..utils import ensure_runtime_dir, runtime_join
|
|
18
|
+
from ..tui import clear_tui, current_tui, open_tui_terminal, terminate_tui
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
|
|
@@ -266,6 +266,16 @@ class AgentServer:
|
|
|
266
266
|
for subscriber, state in list(self.subscribers.items()):
|
|
267
267
|
if not state.thread.is_alive():
|
|
268
268
|
self.subscribers.pop(subscriber, None)
|
|
269
|
+
return len(self.subscribers)
|
|
270
|
+
|
|
271
|
+
def subscriber_count(self):
|
|
272
|
+
if not self.subscriber_lock.acquire(timeout=0.05):
|
|
273
|
+
return len(self.subscribers)
|
|
274
|
+
try:
|
|
275
|
+
self.prune_subscribers()
|
|
276
|
+
return len(self.subscribers)
|
|
277
|
+
finally:
|
|
278
|
+
self.subscriber_lock.release()
|
|
269
279
|
|
|
270
280
|
def close_subscribers(self, reason="server shutdown"):
|
|
271
281
|
self.shutdown_requested.set()
|
|
@@ -277,7 +287,9 @@ class AgentServer:
|
|
|
277
287
|
def subscribe(self, after_sequence=None, client_pid=None):
|
|
278
288
|
after_sequence = int(after_sequence) if after_sequence is not None else None
|
|
279
289
|
client_pid = int(client_pid) if client_pid is not None else None
|
|
280
|
-
|
|
290
|
+
if not self.subscriber_lock.acquire(timeout=1.0):
|
|
291
|
+
raise RuntimeError("event subscription manager is busy")
|
|
292
|
+
try:
|
|
281
293
|
self.shutdown_requested.clear()
|
|
282
294
|
self.prune_subscribers()
|
|
283
295
|
tui = current_tui()
|
|
@@ -297,6 +309,8 @@ class AgentServer:
|
|
|
297
309
|
and int(event.get("sequence") or 0) <= replay_before_sequence
|
|
298
310
|
and self.event_belongs_to_session(event, session_id)
|
|
299
311
|
]
|
|
312
|
+
finally:
|
|
313
|
+
self.subscriber_lock.release()
|
|
300
314
|
def stream():
|
|
301
315
|
try:
|
|
302
316
|
yield modict(
|
|
@@ -321,18 +335,20 @@ class AgentServer:
|
|
|
321
335
|
if payload.get("type") == "server_shutdown":
|
|
322
336
|
break
|
|
323
337
|
finally:
|
|
324
|
-
|
|
338
|
+
if not self.subscriber_lock.acquire(timeout=1.0):
|
|
339
|
+
return
|
|
340
|
+
try:
|
|
325
341
|
self.subscribers.pop(subscriber, None)
|
|
342
|
+
finally:
|
|
343
|
+
self.subscriber_lock.release()
|
|
326
344
|
return stream()
|
|
327
345
|
|
|
328
346
|
def status(self):
|
|
329
|
-
with self.subscriber_lock:
|
|
330
|
-
self.prune_subscribers()
|
|
331
347
|
status = self.runtime.get_status()
|
|
332
348
|
status.update(
|
|
333
349
|
busy=self.busy,
|
|
334
350
|
current_turn_id=self.current_turn_id,
|
|
335
|
-
subscribers=
|
|
351
|
+
subscribers=self.subscriber_count(),
|
|
336
352
|
event_backlog=len(self.events),
|
|
337
353
|
)
|
|
338
354
|
self.persist_current_session()
|
|
@@ -483,198 +499,3 @@ class AgentServer:
|
|
|
483
499
|
raise RuntimeError("agent is busy")
|
|
484
500
|
|
|
485
501
|
|
|
486
|
-
def create_app(agent: Agent | None = None, start_agent=False, process_agent=False, **agent_kwargs):
|
|
487
|
-
from contextlib import asynccontextmanager
|
|
488
|
-
|
|
489
|
-
from fastapi import FastAPI, HTTPException, Request
|
|
490
|
-
from fastapi.responses import StreamingResponse
|
|
491
|
-
from pydantic import BaseModel
|
|
492
|
-
|
|
493
|
-
if process_agent and agent is None:
|
|
494
|
-
if "session" not in agent_kwargs:
|
|
495
|
-
agent_kwargs["session"] = remembered_server_session() or "latest"
|
|
496
|
-
runtime = ProcessAgentRuntime(agent_kwargs=agent_kwargs)
|
|
497
|
-
else:
|
|
498
|
-
runtime = InProcessAgentRuntime(agent or create_server_agent(**agent_kwargs))
|
|
499
|
-
service = AgentServer(runtime=runtime)
|
|
500
|
-
if start_agent:
|
|
501
|
-
service.start()
|
|
502
|
-
|
|
503
|
-
@asynccontextmanager
|
|
504
|
-
async def lifespan(app):
|
|
505
|
-
try:
|
|
506
|
-
yield
|
|
507
|
-
finally:
|
|
508
|
-
service.stop(timeout=1.0)
|
|
509
|
-
|
|
510
|
-
app = FastAPI(title="Codex Agent", version="0.1.0", lifespan=lifespan)
|
|
511
|
-
app.state.agent_service = service
|
|
512
|
-
|
|
513
|
-
class TurnRequest(BaseModel):
|
|
514
|
-
prompt: str
|
|
515
|
-
|
|
516
|
-
class InterruptRequest(BaseModel):
|
|
517
|
-
reason: str = "api"
|
|
518
|
-
|
|
519
|
-
class ConfigUpdateRequest(BaseModel):
|
|
520
|
-
values: dict = {}
|
|
521
|
-
save: bool = True
|
|
522
|
-
|
|
523
|
-
class WakeupRequest(BaseModel):
|
|
524
|
-
prompt: str
|
|
525
|
-
run_at: str | None = None
|
|
526
|
-
delay_seconds: float | None = None
|
|
527
|
-
interval_seconds: float | None = None
|
|
528
|
-
title: str = ""
|
|
529
|
-
|
|
530
|
-
class RestartRequest(BaseModel):
|
|
531
|
-
prompt: str | None = None
|
|
532
|
-
title: str = "post-restart wakeup"
|
|
533
|
-
|
|
534
|
-
def jsonable(value):
|
|
535
|
-
return json.loads(json.dumps(value, ensure_ascii=False, default=str))
|
|
536
|
-
|
|
537
|
-
def busy_guard(call):
|
|
538
|
-
try:
|
|
539
|
-
return call()
|
|
540
|
-
except RuntimeError as exc:
|
|
541
|
-
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
|
542
|
-
except ValueError as exc:
|
|
543
|
-
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
544
|
-
|
|
545
|
-
@app.get("/health")
|
|
546
|
-
def health():
|
|
547
|
-
return {"ok": True, "busy": service.busy, "session_id": service.current_session_id}
|
|
548
|
-
|
|
549
|
-
@app.get("/status")
|
|
550
|
-
def status():
|
|
551
|
-
return jsonable(service.status())
|
|
552
|
-
|
|
553
|
-
@app.get("/config")
|
|
554
|
-
def config():
|
|
555
|
-
return jsonable(service.config())
|
|
556
|
-
|
|
557
|
-
@app.patch("/config")
|
|
558
|
-
def update_config(request: ConfigUpdateRequest):
|
|
559
|
-
return jsonable(busy_guard(lambda: service.update_config(request.values, save=request.save)))
|
|
560
|
-
|
|
561
|
-
@app.get("/session")
|
|
562
|
-
def session():
|
|
563
|
-
return jsonable(service.session())
|
|
564
|
-
|
|
565
|
-
@app.get("/sessions")
|
|
566
|
-
def sessions():
|
|
567
|
-
return jsonable(service.sessions())
|
|
568
|
-
|
|
569
|
-
@app.post("/sessions/new")
|
|
570
|
-
def start_new_session():
|
|
571
|
-
return jsonable(busy_guard(service.start_new_session))
|
|
572
|
-
|
|
573
|
-
@app.post("/sessions/{session_id}/load")
|
|
574
|
-
def load_session(session_id: str):
|
|
575
|
-
return jsonable(busy_guard(lambda: service.load_session(session_id)))
|
|
576
|
-
|
|
577
|
-
@app.delete("/sessions/{session_id}")
|
|
578
|
-
def delete_session(session_id: str):
|
|
579
|
-
return jsonable(busy_guard(lambda: service.delete_session(session_id)))
|
|
580
|
-
|
|
581
|
-
@app.post("/sessions/navigate/{direction}")
|
|
582
|
-
def navigate_session(direction: str):
|
|
583
|
-
return jsonable(busy_guard(lambda: service.navigate_session(direction)))
|
|
584
|
-
|
|
585
|
-
@app.get("/messages")
|
|
586
|
-
def messages():
|
|
587
|
-
return jsonable(service.messages())
|
|
588
|
-
|
|
589
|
-
@app.get("/memory")
|
|
590
|
-
def memory():
|
|
591
|
-
return jsonable(service.memory())
|
|
592
|
-
|
|
593
|
-
@app.get("/tools")
|
|
594
|
-
def tools():
|
|
595
|
-
return jsonable(service.tools())
|
|
596
|
-
|
|
597
|
-
@app.get("/tui")
|
|
598
|
-
def tui():
|
|
599
|
-
return jsonable(service.tui())
|
|
600
|
-
|
|
601
|
-
@app.post("/tui/open")
|
|
602
|
-
def open_tui(request: Request):
|
|
603
|
-
base_url = str(request.base_url).rstrip("/")
|
|
604
|
-
return jsonable(busy_guard(lambda: service.open_tui(base_url=base_url)))
|
|
605
|
-
|
|
606
|
-
@app.post("/tui/close")
|
|
607
|
-
def close_tui():
|
|
608
|
-
return jsonable(service.close_tui())
|
|
609
|
-
|
|
610
|
-
@app.post("/restart")
|
|
611
|
-
@app.post("/restart/")
|
|
612
|
-
def restart(request: RestartRequest):
|
|
613
|
-
return jsonable(busy_guard(lambda: service.restart(**request.model_dump())))
|
|
614
|
-
|
|
615
|
-
@app.get("/wakeups")
|
|
616
|
-
def wakeups(include_done: bool = True):
|
|
617
|
-
return jsonable(service.wakeups(include_done=include_done))
|
|
618
|
-
|
|
619
|
-
@app.post("/wakeups", status_code=201)
|
|
620
|
-
def schedule_wakeup(request: WakeupRequest):
|
|
621
|
-
return jsonable(busy_guard(lambda: service.schedule_wakeup(**request.model_dump())))
|
|
622
|
-
|
|
623
|
-
@app.post("/wakeups/{job_id}/cancel")
|
|
624
|
-
def cancel_wakeup(job_id: str):
|
|
625
|
-
return jsonable(busy_guard(lambda: service.cancel_wakeup(job_id)))
|
|
626
|
-
|
|
627
|
-
@app.delete("/wakeups/{job_id}")
|
|
628
|
-
def delete_wakeup(job_id: str):
|
|
629
|
-
return jsonable(busy_guard(lambda: service.delete_wakeup(job_id)))
|
|
630
|
-
|
|
631
|
-
@app.post("/turns", status_code=202)
|
|
632
|
-
def turns(request: TurnRequest):
|
|
633
|
-
result = busy_guard(lambda: service.submit_prompt(request.prompt))
|
|
634
|
-
return {"accepted": True, **jsonable(result)}
|
|
635
|
-
|
|
636
|
-
@app.post("/interrupt")
|
|
637
|
-
def interrupt(request: InterruptRequest):
|
|
638
|
-
return jsonable(service.interrupt(request.reason))
|
|
639
|
-
|
|
640
|
-
@app.get("/events")
|
|
641
|
-
def events(limit: int | None = None, after_sequence: int | None = None, client_pid: int | None = None):
|
|
642
|
-
try:
|
|
643
|
-
subscription = service.subscribe(after_sequence=after_sequence, client_pid=client_pid)
|
|
644
|
-
except RuntimeError as exc:
|
|
645
|
-
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
|
646
|
-
|
|
647
|
-
def stream():
|
|
648
|
-
count = 0
|
|
649
|
-
for payload in subscription:
|
|
650
|
-
lines = []
|
|
651
|
-
if payload.get("id"):
|
|
652
|
-
lines.append(f"id: {payload.id}")
|
|
653
|
-
lines.append(f"event: {payload.get('type', 'message')}")
|
|
654
|
-
lines.append(f"data: {json.dumps(payload, ensure_ascii=False, default=str)}")
|
|
655
|
-
yield "\n".join(lines) + "\n\n"
|
|
656
|
-
count += 1
|
|
657
|
-
if limit is not None and count >= limit:
|
|
658
|
-
break
|
|
659
|
-
return StreamingResponse(stream(), media_type="text/event-stream")
|
|
660
|
-
|
|
661
|
-
@app.get("/events/replay")
|
|
662
|
-
def replay_events(before_sequence: int | None = None, session_id: str | None = None):
|
|
663
|
-
return jsonable(service.replay_events(before_sequence=before_sequence, session_id=session_id))
|
|
664
|
-
|
|
665
|
-
return app
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
app = None
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
def main():
|
|
672
|
-
import uvicorn
|
|
673
|
-
|
|
674
|
-
global app
|
|
675
|
-
app = create_app(start_agent=True, process_agent=True)
|
|
676
|
-
uvicorn.run(app, host="127.0.0.1", port=8765, timeout_graceful_shutdown=2)
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
if __name__ == "__main__":
|
|
680
|
-
main()
|