agentseek-api 0.1.0__tar.gz → 0.2.0__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.
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/.env.example +4 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/.github/workflows/ci.yml +3 -0
- agentseek_api-0.2.0/CHANGELOG.md +22 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/PKG-INFO +14 -6
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/README.md +11 -3
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/README.zh-CN.md +10 -3
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/pyproject.toml +3 -3
- agentseek_api-0.2.0/src/agentseek_api/__init__.py +1 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/a2a_server.py +3 -3
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/api/crons.py +20 -11
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/api/runs.py +9 -32
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/api/threads.py +8 -28
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/core/database.py +55 -1
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/core/oceanbase_checkpointer.py +0 -1
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/core/orm.py +5 -1
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/models/api.py +60 -6
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/services/cron_models.py +6 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/services/cron_scheduler.py +39 -1
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/services/cron_service.py +142 -26
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/services/run_executor.py +32 -4
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/services/run_jobs.py +39 -5
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/services/sse.py +3 -1
- agentseek_api-0.2.0/src/agentseek_api/services/stream_modes.py +60 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/services/stream_persistence.py +152 -18
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/services/thread_protocol.py +51 -17
- agentseek_api-0.2.0/src/agentseek_api/services/thread_service.py +116 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/settings.py +2 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/e2e/conftest.py +35 -1
- agentseek_api-0.2.0/tests/e2e/test_cron_api_live.py +206 -0
- agentseek_api-0.2.0/tests/e2e/test_cron_scheduler_live.py +162 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_auth_coverage_gaps.py +0 -2
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_auth_handlers.py +18 -1
- agentseek_api-0.2.0/tests/integration/test_cron_api.py +659 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_langsmith_compat_coverage.py +11 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_runs_compat.py +17 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_scheduler_runtime.py +335 -1
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_stream_persistence.py +46 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_a2a_server.py +20 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_auth_internals.py +3 -3
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_cli.py +15 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_content_type_fix.py +0 -1
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_cron_service.py +48 -2
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_database_manager.py +169 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_default_assistants.py +0 -1
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_run_executor.py +32 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_run_jobs.py +91 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_scheduler.py +89 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_sse_keepalive.py +6 -0
- agentseek_api-0.2.0/tests/unit/test_stream_modes.py +42 -0
- agentseek_api-0.2.0/tests/unit/test_stream_persistence_helpers.py +438 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_thread_protocol.py +145 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_thread_routes_unit.py +22 -8
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/uv.lock +6 -6
- agentseek_api-0.1.0/src/agentseek_api/__init__.py +0 -1
- agentseek_api-0.1.0/src/agentseek_api/services/thread_service.py +0 -64
- agentseek_api-0.1.0/tests/integration/test_cron_api.py +0 -223
- agentseek_api-0.1.0/tests/unit/test_stream_persistence_helpers.py +0 -194
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/.dockerignore +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/.github/workflows/live-provider-streaming.yml +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/.github/workflows/release.yml +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/.gitignore +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/AGENTS.md +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/CONTRIBUTING.md +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/LICENSE +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/Makefile +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/README.md +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/__init__.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/assistant_config/agentseek.json +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/assistant_config/graph.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/auth/api_key_auth.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/auth/bearer_token_auth.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/auth/custom_backend.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/auth/jwt.md +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/auth/jwt_auth.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/custom_routes/app.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/docker_ci_auth/auth_backend.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/docker_ci_auth/manifest.json +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/external_graph/__init__.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/external_graph/graph.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/external_graph/manifest.json +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/external_graph/run.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/graphs/__init__.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/graphs/react_agent/__init__.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/graphs/react_agent/graph.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/graphs/store_memory/graph.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/graphs/stress_test/__init__.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/graphs/stress_test/graph.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/graphs/stress_tool_agent/__init__.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/graphs/stress_tool_agent/graph.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/graphs/subgraph_agent/__init__.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/graphs/subgraph_agent/graph.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/graphs/subgraph_hitl_agent/__init__.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/graphs/subgraph_hitl_agent/graph.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/live_provider_graphs/__init__.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/live_provider_graphs/graph.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/live_provider_graphs/manifest.json +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/minimal_agentseek/agentseek.json +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/minimal_agentseek/graph.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/run_sample_graphs.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/examples/sample_graphs_manifest.json +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/scripts/build_live_provider_matrix.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/scripts/seekdb_checkpoint_smoke.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/scripts/seekdb_embed_launcher.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/scripts/test-checkpoints.sh +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/scripts/test-cli-dev-samples.sh +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/scripts/test-cli-docker.sh +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/scripts/test-live-provider.sh +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/scripts/test-redis-runtime.sh +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/scripts/test-seekdb.sh +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/scripts/test_cli_config_autodiscovery.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/scripts/test_cli_dev_studio_smoke.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/scripts/test_cli_embed_serve_smoke.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/scripts/test_cli_serve_smoke.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/scripts/verify_docker_api.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/api/__init__.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/api/assistants.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/api/stateless_runs.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/api/store.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/api/streaming.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/cli.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/core/__init__.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/core/a2a_config.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/core/app_loader.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/core/auth_deps.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/core/auth_middleware.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/core/config_file.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/core/content_type_fix.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/core/cors_config.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/core/http_config.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/core/mcp_config.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/core/runtime_store.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/core/store_config.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/main.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/mcp_server.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/models/__init__.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/models/auth.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/models/protocol.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/scheduler.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/services/__init__.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/services/cron_rrule.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/services/cron_webhooks.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/services/default_assistants.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/services/executor.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/services/langgraph_service.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/services/redis_queue.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/services/run_preparation.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/services/run_state.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/services/sample_graphs.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/services/thread_checkpoint_store.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/src/agentseek_api/worker.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/__init__.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/conftest.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/e2e/__init__.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/e2e/e2e_inprocess_flow.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/e2e/e2e_live_http_flow.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/e2e/e2e_live_http_multi_graph.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/e2e/e2e_live_http_resume_flow.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/e2e/fixtures/langgraph.store-e2e.json +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/e2e/live_provider_helpers.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/e2e/test_a2a_live.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/e2e/test_langsmith_compat_live.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/e2e/test_live_provider_api.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/e2e/test_live_provider_mcp.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/e2e/test_mcp_live.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/e2e/test_real_api_flow.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_a2a_endpoint.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_agent_card.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_agents_aliases.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_assistants_compat.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_assistants_crud.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_assistants_ordering.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_auth_route_enforcement.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_auth_scope_matrix.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_core_api_flow.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_langsmith_compat_extra.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_live_provider_streaming.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_mcp_endpoint.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_metadata_db_config.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_protocol_v2_streaming.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_run_cancellation_async.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_runs_crud.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_runs_ordering.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_runs_resume.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_runs_streaming.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_runs_streaming_errors.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_runs_validation.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_runs_wait_and_get.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_runs_wait_timeout.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_stateless_runs_crud.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_store_api.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_system_endpoints.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_threads_compat.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_threads_crud.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/integration/test_threads_ordering.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_a2a_config.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_app_loader.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_assistant_routes_unit.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_auth.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_auth_deps.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_checkpoint_guards.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_cron_rrule.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_cron_webhooks.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_custom_routers.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_examples.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_executor.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_graph_manifest.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_http_config.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_langgraph_service.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_live_provider_e2e_config.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_live_provider_graphs.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_live_provider_matrix.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_mcp_config.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_mcp_server.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_oceanbase_checkpointer.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_openapi_auth_config.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_redis_queue.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_run_preparation.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_run_state.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_runtime_store.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_sample_graphs.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_scheduler_process.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_seekdb_checkpoint_smoke.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_seekdb_embed_launcher.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_store_config.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_thread_checkpoint_store.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_thread_service.py +0 -0
- {agentseek_api-0.1.0 → agentseek_api-0.2.0}/tests/unit/test_worker.py +0 -0
|
@@ -7,6 +7,10 @@ AGENTSEEK_GRAPHS=
|
|
|
7
7
|
# If not set, all requests pass through as default_user (noop).
|
|
8
8
|
AUTH_MODULE_PATH=
|
|
9
9
|
|
|
10
|
+
# Redis worker stream replay buffer.
|
|
11
|
+
# REDIS_STREAM_MAXLEN=10000
|
|
12
|
+
# REDIS_STREAM_TTL_SECONDS=3600
|
|
13
|
+
|
|
10
14
|
# Embedded SeekDB — set to true for a zero-config local backend.
|
|
11
15
|
# Data stored in SEEKDB_EMBED_DIR (default: ~/.agentseek/seekdb_data).
|
|
12
16
|
# Requires: uv sync --dev --extra embedded
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable changes to AgentSeek API are documented in this file.
|
|
4
|
+
|
|
5
|
+
## 0.2.0 - 2026-07-12
|
|
6
|
+
|
|
7
|
+
### Highlights
|
|
8
|
+
|
|
9
|
+
- Expanded the Crons API to align its request, response, filtering, sorting,
|
|
10
|
+
selection, and lifecycle behavior with the LangGraph Platform contract.
|
|
11
|
+
- Moved Redis-worker run and protocol event persistence from SQL to bounded
|
|
12
|
+
Redis Streams, removing metadata-database writes from the streaming hot path.
|
|
13
|
+
- Preserved UTF-8 output across SSE, wait, and A2A JSON responses while keeping
|
|
14
|
+
active thread event retention bounded without dropping live events.
|
|
15
|
+
- Upgraded checkpoint integration to `langchain-oceanbase` 0.6.0.
|
|
16
|
+
|
|
17
|
+
### Upgrade notes
|
|
18
|
+
|
|
19
|
+
- Drain active Redis-worker runs before upgrading. Stream-event rows written to
|
|
20
|
+
SQL by earlier versions are not imported into Redis Streams.
|
|
21
|
+
- Redis stream replay is bounded by `REDIS_STREAM_MAXLEN` and
|
|
22
|
+
`REDIS_STREAM_TTL_SECONDS`; review these settings for long-running workloads.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentseek-api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: AgentSeek API core runtime with OceanBase checkpoints.
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -12,7 +12,7 @@ Requires-Dist: fastapi>=0.110.1
|
|
|
12
12
|
Requires-Dist: greenlet>=3.1.0
|
|
13
13
|
Requires-Dist: langchain-anthropic>=1.0.0
|
|
14
14
|
Requires-Dist: langchain-core>=1.0.0
|
|
15
|
-
Requires-Dist: langchain-oceanbase==0.
|
|
15
|
+
Requires-Dist: langchain-oceanbase==0.6.0
|
|
16
16
|
Requires-Dist: langchain-openai>=1.0.0
|
|
17
17
|
Requires-Dist: langchain>=0.3.9
|
|
18
18
|
Requires-Dist: langgraph-sdk>=0.3.5
|
|
@@ -26,13 +26,15 @@ Requires-Dist: scalar-fastapi>=1.0.3
|
|
|
26
26
|
Requires-Dist: sqlalchemy>=2.0.0
|
|
27
27
|
Requires-Dist: uvicorn>=0.30.0
|
|
28
28
|
Provides-Extra: embedded
|
|
29
|
-
Requires-Dist: langchain-oceanbase[pyseekdb]==0.
|
|
29
|
+
Requires-Dist: langchain-oceanbase[pyseekdb]==0.6.0; extra == 'embedded'
|
|
30
30
|
Description-Content-Type: text/markdown
|
|
31
31
|
|
|
32
32
|
# AgentSeek API
|
|
33
33
|
|
|
34
34
|
**English** | [中文](README.zh-CN.md)
|
|
35
35
|
|
|
36
|
+
[](https://pypi.org/project/agentseek-api/) [](https://pypi.org/project/agentseek-api/) [](https://github.com/ob-labs/agentseek-api/actions/workflows/ci.yml?query=branch%3Amain)
|
|
37
|
+
|
|
36
38
|
> [!WARNING]
|
|
37
39
|
> This project is under active development and is **not production-ready**.
|
|
38
40
|
> Pull requests for bug fixes and enhancements are warmly welcomed!
|
|
@@ -214,7 +216,7 @@ When the server starts it prints the banner and local URLs:
|
|
|
214
216
|
╠═╣│ ┬├┤ │││ │ ╚═╗├┤ ├┤ ├┴┐
|
|
215
217
|
╩ ╩└─┘└─┘┘└┘ ┴ ╚═╝└─┘└─┘┴ ┴
|
|
216
218
|
|
|
217
|
-
AgentSeek v0.
|
|
219
|
+
AgentSeek v0.2.0
|
|
218
220
|
|
|
219
221
|
- 🚀 API: http://localhost:2024
|
|
220
222
|
- 📚 Docs: http://localhost:2024/docs
|
|
@@ -653,6 +655,8 @@ parent api build --config ./langgraph.json -t my-api:dev
|
|
|
653
655
|
- `REDIS_RUN_PROCESSING_KEY=agentseek:runs:processing`
|
|
654
656
|
- `REDIS_WORKER_LOCK_KEY=agentseek:worker:active`
|
|
655
657
|
- `REDIS_WORKER_LOCK_TTL_SECONDS=30`
|
|
658
|
+
- `REDIS_STREAM_MAXLEN=10000`
|
|
659
|
+
- `REDIS_STREAM_TTL_SECONDS=3600`
|
|
656
660
|
- `METADATA_DB_BACKEND=auto` normalizes drivers:
|
|
657
661
|
- PostgreSQL: `postgresql+asyncpg://...`
|
|
658
662
|
- OceanBase / MySQL: `mysql+aiomysql://...`
|
|
@@ -665,8 +669,12 @@ parent api build --config ./langgraph.json -t my-api:dev
|
|
|
665
669
|
|
|
666
670
|
### Durable execution
|
|
667
671
|
|
|
668
|
-
- Redis mode
|
|
669
|
-
|
|
672
|
+
- Redis mode stores run stream events and protocol stream events in bounded
|
|
673
|
+
Redis Streams, so replay does not depend on API-process memory and streaming
|
|
674
|
+
writes do not put the metadata database on the hot path.
|
|
675
|
+
- When upgrading from a version that stored Redis-worker stream events in the
|
|
676
|
+
metadata database, drain active runs first. Existing SQL stream-event rows
|
|
677
|
+
are not imported into Redis.
|
|
670
678
|
- Interrupted runs can be resumed after worker restart as long as Redis and the
|
|
671
679
|
metadata database stay available.
|
|
672
680
|
- The worker owns a renewable Redis lease and exits if that lease is lost,
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
**English** | [中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
+
[](https://pypi.org/project/agentseek-api/) [](https://pypi.org/project/agentseek-api/) [](https://github.com/ob-labs/agentseek-api/actions/workflows/ci.yml?query=branch%3Amain)
|
|
6
|
+
|
|
5
7
|
> [!WARNING]
|
|
6
8
|
> This project is under active development and is **not production-ready**.
|
|
7
9
|
> Pull requests for bug fixes and enhancements are warmly welcomed!
|
|
@@ -183,7 +185,7 @@ When the server starts it prints the banner and local URLs:
|
|
|
183
185
|
╠═╣│ ┬├┤ │││ │ ╚═╗├┤ ├┤ ├┴┐
|
|
184
186
|
╩ ╩└─┘└─┘┘└┘ ┴ ╚═╝└─┘└─┘┴ ┴
|
|
185
187
|
|
|
186
|
-
AgentSeek v0.
|
|
188
|
+
AgentSeek v0.2.0
|
|
187
189
|
|
|
188
190
|
- 🚀 API: http://localhost:2024
|
|
189
191
|
- 📚 Docs: http://localhost:2024/docs
|
|
@@ -622,6 +624,8 @@ parent api build --config ./langgraph.json -t my-api:dev
|
|
|
622
624
|
- `REDIS_RUN_PROCESSING_KEY=agentseek:runs:processing`
|
|
623
625
|
- `REDIS_WORKER_LOCK_KEY=agentseek:worker:active`
|
|
624
626
|
- `REDIS_WORKER_LOCK_TTL_SECONDS=30`
|
|
627
|
+
- `REDIS_STREAM_MAXLEN=10000`
|
|
628
|
+
- `REDIS_STREAM_TTL_SECONDS=3600`
|
|
625
629
|
- `METADATA_DB_BACKEND=auto` normalizes drivers:
|
|
626
630
|
- PostgreSQL: `postgresql+asyncpg://...`
|
|
627
631
|
- OceanBase / MySQL: `mysql+aiomysql://...`
|
|
@@ -634,8 +638,12 @@ parent api build --config ./langgraph.json -t my-api:dev
|
|
|
634
638
|
|
|
635
639
|
### Durable execution
|
|
636
640
|
|
|
637
|
-
- Redis mode
|
|
638
|
-
|
|
641
|
+
- Redis mode stores run stream events and protocol stream events in bounded
|
|
642
|
+
Redis Streams, so replay does not depend on API-process memory and streaming
|
|
643
|
+
writes do not put the metadata database on the hot path.
|
|
644
|
+
- When upgrading from a version that stored Redis-worker stream events in the
|
|
645
|
+
metadata database, drain active runs first. Existing SQL stream-event rows
|
|
646
|
+
are not imported into Redis.
|
|
639
647
|
- Interrupted runs can be resumed after worker restart as long as Redis and the
|
|
640
648
|
metadata database stay available.
|
|
641
649
|
- The worker owns a renewable Redis lease and exits if that lease is lost,
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | **中文**
|
|
4
4
|
|
|
5
|
+
[](https://pypi.org/project/agentseek-api/) [](https://pypi.org/project/agentseek-api/) [](https://github.com/ob-labs/agentseek-api/actions/workflows/ci.yml?query=branch%3Amain)
|
|
6
|
+
|
|
5
7
|
> [!WARNING]
|
|
6
8
|
> 本项目正在积极开发中,**尚未达到生产可用状态**。
|
|
7
9
|
> 欢迎提交 Pull Request 来修复 Bug 或贡献增强功能!
|
|
@@ -179,7 +181,7 @@ uv run agentseek-api dev --config ./langgraph.json
|
|
|
179
181
|
╠═╣│ ┬├┤ │││ │ ╚═╗├┤ ├┤ ├┴┐
|
|
180
182
|
╩ ╩└─┘└─┘┘└┘ ┴ ╚═╝└─┘└─┘┴ ┴
|
|
181
183
|
|
|
182
|
-
AgentSeek v0.
|
|
184
|
+
AgentSeek v0.2.0
|
|
183
185
|
|
|
184
186
|
- 🚀 API: http://localhost:2024
|
|
185
187
|
- 📚 Docs: http://localhost:2024/docs
|
|
@@ -616,6 +618,8 @@ parent api build --config ./langgraph.json -t my-api:dev
|
|
|
616
618
|
- `REDIS_RUN_PROCESSING_KEY=agentseek:runs:processing`
|
|
617
619
|
- `REDIS_WORKER_LOCK_KEY=agentseek:worker:active`
|
|
618
620
|
- `REDIS_WORKER_LOCK_TTL_SECONDS=30`
|
|
621
|
+
- `REDIS_STREAM_MAXLEN=10000`
|
|
622
|
+
- `REDIS_STREAM_TTL_SECONDS=3600`
|
|
619
623
|
- `METADATA_DB_BACKEND=auto` 会对驱动进行归一化:
|
|
620
624
|
- PostgreSQL:`postgresql+asyncpg://...`
|
|
621
625
|
- OceanBase / MySQL:`mysql+aiomysql://...`
|
|
@@ -628,8 +632,11 @@ parent api build --config ./langgraph.json -t my-api:dev
|
|
|
628
632
|
|
|
629
633
|
### 持久化执行
|
|
630
634
|
|
|
631
|
-
- Redis 模式会把 run 流事件与 protocol
|
|
632
|
-
|
|
635
|
+
- Redis 模式会把 run 流事件与 protocol 流事件存入有界 Redis Stream,
|
|
636
|
+
这样流回放既不依赖 API 进程内存,也不会让流式写入占用元数据数据库
|
|
637
|
+
的关键路径。
|
|
638
|
+
- 从使用元数据数据库保存 Redis worker 流事件的旧版本升级时,请先等待
|
|
639
|
+
活跃 run 执行完成;已有的 SQL 流事件不会导入 Redis。
|
|
633
640
|
- 只要 Redis 与元数据数据库仍然可用,被中断的 run 可以在 worker 重启
|
|
634
641
|
后恢复。
|
|
635
642
|
- Worker 持有一个可续期的 Redis lease,在 lease 丢失时会主动退出,从而
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "agentseek-api"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "AgentSeek API core runtime with OceanBase checkpoints."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -20,7 +20,7 @@ dependencies = [
|
|
|
20
20
|
"langchain-core>=1.0.0",
|
|
21
21
|
"langchain-openai>=1.0.0",
|
|
22
22
|
"langchain-anthropic>=1.0.0",
|
|
23
|
-
"langchain-oceanbase==0.
|
|
23
|
+
"langchain-oceanbase==0.6.0",
|
|
24
24
|
"cryptography>=45.0.0",
|
|
25
25
|
"pymysql>=1.1.0",
|
|
26
26
|
"langchain>=0.3.9",
|
|
@@ -30,7 +30,7 @@ dependencies = [
|
|
|
30
30
|
|
|
31
31
|
[project.optional-dependencies]
|
|
32
32
|
embedded = [
|
|
33
|
-
"langchain-oceanbase[pyseekdb]==0.
|
|
33
|
+
"langchain-oceanbase[pyseekdb]==0.6.0",
|
|
34
34
|
]
|
|
35
35
|
|
|
36
36
|
[project.scripts]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.0"
|
|
@@ -282,7 +282,7 @@ def _message_content(message: BaseMessage) -> str:
|
|
|
282
282
|
content = getattr(message, "content", "")
|
|
283
283
|
if isinstance(content, str):
|
|
284
284
|
return content
|
|
285
|
-
return json.dumps(content, default=str)
|
|
285
|
+
return json.dumps(content, ensure_ascii=False, default=str)
|
|
286
286
|
|
|
287
287
|
|
|
288
288
|
def _extract_chunk_messages(chunk: Any) -> list[BaseMessage]:
|
|
@@ -366,7 +366,7 @@ def _extract_output_text(extracted: Any) -> str:
|
|
|
366
366
|
message_texts = [_message_content(message) for message in messages if isinstance(message, BaseMessage)]
|
|
367
367
|
if message_texts:
|
|
368
368
|
return message_texts[-1]
|
|
369
|
-
return json.dumps(extracted, default=str)
|
|
369
|
+
return json.dumps(extracted, ensure_ascii=False, default=str)
|
|
370
370
|
|
|
371
371
|
|
|
372
372
|
async def _invoke_a2a_graph(
|
|
@@ -558,7 +558,7 @@ def _canonical_a2a_method(method: str) -> tuple[str, bool]:
|
|
|
558
558
|
|
|
559
559
|
|
|
560
560
|
def _sse_jsonrpc_event(*, request_id: Any, result: dict[str, Any]) -> str:
|
|
561
|
-
return f"event: message\ndata: {json.dumps(_jsonrpc_result(request_id, result))}\n\n"
|
|
561
|
+
return f"event: message\ndata: {json.dumps(_jsonrpc_result(request_id, result), ensure_ascii=False)}\n\n"
|
|
562
562
|
|
|
563
563
|
|
|
564
564
|
async def handle_a2a_request(
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from sqlalchemy import select
|
|
2
2
|
|
|
3
3
|
from fastapi import APIRouter, Depends, HTTPException
|
|
4
|
-
from fastapi.responses import Response
|
|
4
|
+
from fastapi.responses import JSONResponse, Response
|
|
5
5
|
|
|
6
|
-
from agentseek_api.core.auth_deps import authorize,
|
|
6
|
+
from agentseek_api.core.auth_deps import authorize, get_current_user
|
|
7
7
|
from agentseek_api.core.database import db_manager
|
|
8
8
|
from agentseek_api.core.orm import Assistant, Thread
|
|
9
9
|
from agentseek_api.models.api import (
|
|
@@ -14,6 +14,7 @@ from agentseek_api.models.api import (
|
|
|
14
14
|
CronRead,
|
|
15
15
|
CronSearchRequest,
|
|
16
16
|
CronSearchResponse,
|
|
17
|
+
ThreadCronCreate,
|
|
17
18
|
)
|
|
18
19
|
from agentseek_api.models.auth import User
|
|
19
20
|
from agentseek_api.services import cron_service
|
|
@@ -36,7 +37,7 @@ async def _create_cron(
|
|
|
36
37
|
*,
|
|
37
38
|
assistant_id: str,
|
|
38
39
|
thread_id: str | None,
|
|
39
|
-
payload: CronCreate,
|
|
40
|
+
payload: CronCreate | ThreadCronCreate,
|
|
40
41
|
user: User,
|
|
41
42
|
) -> CronRead:
|
|
42
43
|
try:
|
|
@@ -45,7 +46,7 @@ async def _create_cron(
|
|
|
45
46
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
46
47
|
|
|
47
48
|
|
|
48
|
-
@router.post("/runs/crons", response_model=CronRead)
|
|
49
|
+
@router.post("/runs/crons", response_model=CronRead, response_model_exclude_none=True)
|
|
49
50
|
async def create_stateless_cron(payload: CronCreate, user: User = Depends(get_current_user)) -> CronRead:
|
|
50
51
|
value: dict = {
|
|
51
52
|
"metadata": dict(payload.metadata) if payload.metadata else {},
|
|
@@ -57,8 +58,8 @@ async def create_stateless_cron(payload: CronCreate, user: User = Depends(get_cu
|
|
|
57
58
|
return await _create_cron(assistant_id=resolved_id, thread_id=None, payload=payload, user=user)
|
|
58
59
|
|
|
59
60
|
|
|
60
|
-
@router.post("/threads/{thread_id}/runs/crons", response_model=CronRead)
|
|
61
|
-
async def create_thread_cron(thread_id: str, payload:
|
|
61
|
+
@router.post("/threads/{thread_id}/runs/crons", response_model=CronRead, response_model_exclude_none=True)
|
|
62
|
+
async def create_thread_cron(thread_id: str, payload: ThreadCronCreate, user: User = Depends(get_current_user)) -> CronRead:
|
|
62
63
|
value: dict = {
|
|
63
64
|
"metadata": dict(payload.metadata) if payload.metadata else {},
|
|
64
65
|
"assistant_id": payload.assistant_id,
|
|
@@ -75,10 +76,18 @@ async def create_thread_cron(thread_id: str, payload: CronCreate, user: User = D
|
|
|
75
76
|
return await _create_cron(assistant_id=resolved_id, thread_id=thread_id, payload=payload, user=user)
|
|
76
77
|
|
|
77
78
|
|
|
78
|
-
@router.post("/runs/crons/search", response_model=CronSearchResponse)
|
|
79
|
-
async def search_crons(payload: CronSearchRequest, user: User = Depends(get_current_user)) -> CronSearchResponse:
|
|
79
|
+
@router.post("/runs/crons/search", response_model=CronSearchResponse, response_model_exclude_none=True)
|
|
80
|
+
async def search_crons(payload: CronSearchRequest, user: User = Depends(get_current_user)) -> CronSearchResponse | JSONResponse:
|
|
80
81
|
filters = await authorize(user, "crons", "search", {})
|
|
81
|
-
|
|
82
|
+
result = await cron_service.search_crons(payload=payload, user=user, filters=filters)
|
|
83
|
+
if payload.select is not None:
|
|
84
|
+
fields = set(payload.select)
|
|
85
|
+
# exclude_none keeps the select path's null handling consistent with the
|
|
86
|
+
# default path (response_model_exclude_none) and the single-cron routes.
|
|
87
|
+
return JSONResponse(
|
|
88
|
+
content={"items": [item.model_dump(mode="json", include=fields, exclude_none=True) for item in result.items]}
|
|
89
|
+
)
|
|
90
|
+
return result
|
|
82
91
|
|
|
83
92
|
|
|
84
93
|
@router.post("/runs/crons/count", response_model=CronCountResponse)
|
|
@@ -87,13 +96,13 @@ async def count_crons(payload: CronCountRequest, user: User = Depends(get_curren
|
|
|
87
96
|
return await cron_service.count_crons(payload=payload, user=user, filters=filters)
|
|
88
97
|
|
|
89
98
|
|
|
90
|
-
@router.get("/runs/crons/{cron_id}", response_model=CronRead)
|
|
99
|
+
@router.get("/runs/crons/{cron_id}", response_model=CronRead, response_model_exclude_none=True)
|
|
91
100
|
async def get_cron(cron_id: str, user: User = Depends(get_current_user)) -> CronRead:
|
|
92
101
|
filters = await authorize(user, "crons", "read", {"cron_id": cron_id})
|
|
93
102
|
return await cron_service.get_cron(cron_id=cron_id, user=user, filters=filters)
|
|
94
103
|
|
|
95
104
|
|
|
96
|
-
@router.patch("/runs/crons/{cron_id}", response_model=CronRead)
|
|
105
|
+
@router.patch("/runs/crons/{cron_id}", response_model=CronRead, response_model_exclude_none=True)
|
|
97
106
|
async def patch_cron(cron_id: str, payload: CronPatch, user: User = Depends(get_current_user)) -> CronRead:
|
|
98
107
|
filters = await authorize(user, "crons", "update", {"cron_id": cron_id})
|
|
99
108
|
try:
|
|
@@ -31,6 +31,10 @@ from agentseek_api.services.stream_persistence import (
|
|
|
31
31
|
parse_last_event_id,
|
|
32
32
|
)
|
|
33
33
|
from agentseek_api.services.sse import iter_with_sse_keepalives, safe_json_dumps, sse_keepalive_comment
|
|
34
|
+
from agentseek_api.services.stream_modes import (
|
|
35
|
+
SUPPORTED_RUN_STREAM_MODES,
|
|
36
|
+
normalize_stream_modes as _normalize_stream_modes_shared,
|
|
37
|
+
)
|
|
34
38
|
from agentseek_api.services.thread_protocol import thread_protocol_broker
|
|
35
39
|
from agentseek_api.settings import settings
|
|
36
40
|
|
|
@@ -39,8 +43,6 @@ router = APIRouter(prefix="/threads/{thread_id}/runs", tags=["Thread Runs"])
|
|
|
39
43
|
TERMINAL_RUN_STATUSES = ("success", "error", "interrupted")
|
|
40
44
|
REDIS_STREAM_POLL_INTERVAL_SECONDS = 0.05
|
|
41
45
|
REDIS_STREAM_TERMINAL_IDLE_POLLS = 2
|
|
42
|
-
SUPPORTED_RUN_STREAM_MODES = {"values", "updates", "messages", "messages-tuple", "debug", "events", "tasks", "checkpoints", "custom"}
|
|
43
|
-
RUN_STREAM_MODE_ALIASES: dict[str, str] = {}
|
|
44
46
|
RUN_CHECKPOINT_ID_METADATA_KEY = "__agentseek_checkpoint_id"
|
|
45
47
|
|
|
46
48
|
|
|
@@ -107,35 +109,10 @@ def _uses_redis_executor() -> bool:
|
|
|
107
109
|
|
|
108
110
|
|
|
109
111
|
def _normalize_stream_modes(stream_mode: str | list[str] | None) -> list[str]:
|
|
110
|
-
|
|
111
|
-
return
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
modes: list[str] = []
|
|
115
|
-
invalid_modes: list[str] = []
|
|
116
|
-
|
|
117
|
-
if not raw_modes:
|
|
118
|
-
invalid_modes.append("<empty>")
|
|
119
|
-
|
|
120
|
-
for mode in raw_modes:
|
|
121
|
-
raw_mode = str(mode).strip()
|
|
122
|
-
if not raw_mode:
|
|
123
|
-
invalid_modes.append("<empty>")
|
|
124
|
-
continue
|
|
125
|
-
normalized = RUN_STREAM_MODE_ALIASES.get(raw_mode, raw_mode)
|
|
126
|
-
if normalized not in modes:
|
|
127
|
-
modes.append(normalized)
|
|
128
|
-
|
|
129
|
-
unsupported = invalid_modes + [mode for mode in modes if mode not in SUPPORTED_RUN_STREAM_MODES]
|
|
130
|
-
if unsupported:
|
|
131
|
-
raise HTTPException(
|
|
132
|
-
status_code=422,
|
|
133
|
-
detail=(
|
|
134
|
-
"Unsupported stream_mode value(s): "
|
|
135
|
-
f"{', '.join(unsupported)}. Supported values: {', '.join(sorted(SUPPORTED_RUN_STREAM_MODES))}."
|
|
136
|
-
),
|
|
137
|
-
)
|
|
138
|
-
return modes
|
|
112
|
+
try:
|
|
113
|
+
return _normalize_stream_modes_shared(stream_mode)
|
|
114
|
+
except ValueError as exc:
|
|
115
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
139
116
|
|
|
140
117
|
|
|
141
118
|
def _parse_stream_mode_query_param(stream_mode: list[str] | None) -> list[str] | None:
|
|
@@ -275,7 +252,7 @@ def _wait_json_stream_response(
|
|
|
275
252
|
raise
|
|
276
253
|
yield b"\n"
|
|
277
254
|
payload = await _wait_response_payload(current_run, user=user)
|
|
278
|
-
yield
|
|
255
|
+
yield safe_json_dumps(jsonable_encoder(payload), separators=(",", ":")).encode("utf-8")
|
|
279
256
|
finally:
|
|
280
257
|
if cancel_on_disconnect:
|
|
281
258
|
try:
|
|
@@ -48,7 +48,11 @@ from agentseek_api.services.thread_checkpoint_store import (
|
|
|
48
48
|
snapshot_to_payload,
|
|
49
49
|
)
|
|
50
50
|
from agentseek_api.services.thread_protocol import thread_protocol_broker
|
|
51
|
-
from agentseek_api.services.thread_service import
|
|
51
|
+
from agentseek_api.services.thread_service import (
|
|
52
|
+
create_thread_for_user,
|
|
53
|
+
delete_threads_cascade,
|
|
54
|
+
to_read_model,
|
|
55
|
+
)
|
|
52
56
|
from agentseek_api.settings import settings
|
|
53
57
|
|
|
54
58
|
router = APIRouter(prefix="/threads", tags=["Threads"])
|
|
@@ -59,18 +63,6 @@ REDIS_STREAM_TERMINAL_IDLE_POLLS = 2
|
|
|
59
63
|
THREAD_STREAM_CHANNELS = ["input", "lifecycle", "messages", "tools", "values"]
|
|
60
64
|
|
|
61
65
|
|
|
62
|
-
async def _best_effort_checkpointer_call(method_name: str, *args: object, **kwargs: object) -> None:
|
|
63
|
-
method = getattr(db_manager.get_langgraph_checkpointer(), method_name, None)
|
|
64
|
-
if method is None:
|
|
65
|
-
return
|
|
66
|
-
try:
|
|
67
|
-
result = method(*args, **kwargs)
|
|
68
|
-
if hasattr(result, "__await__"):
|
|
69
|
-
await result
|
|
70
|
-
except NotImplementedError:
|
|
71
|
-
return
|
|
72
|
-
|
|
73
|
-
|
|
74
66
|
logger = logging.getLogger(__name__)
|
|
75
67
|
|
|
76
68
|
@functools.lru_cache(maxsize=64)
|
|
@@ -461,23 +453,11 @@ async def delete_thread(thread_id: str, user: User = Depends(get_current_user))
|
|
|
461
453
|
|
|
462
454
|
session_factory = db_manager.get_session_factory()
|
|
463
455
|
async with session_factory() as session:
|
|
464
|
-
stmt = select(Thread).where(Thread.thread_id == thread_id)
|
|
456
|
+
stmt = select(Thread.thread_id).where(Thread.thread_id == thread_id)
|
|
465
457
|
stmt = apply_metadata_filters(stmt, Thread, filters)
|
|
466
|
-
|
|
467
|
-
if row is None:
|
|
458
|
+
if await session.scalar(stmt) is None:
|
|
468
459
|
raise HTTPException(status_code=404, detail="Thread not found")
|
|
469
|
-
|
|
470
|
-
await session.scalars(select(Run.run_id).where(Run.thread_id == thread_id))
|
|
471
|
-
).all()
|
|
472
|
-
await session.execute(delete(Run).where(Run.thread_id == thread_id))
|
|
473
|
-
await session.delete(row)
|
|
474
|
-
await session.commit()
|
|
475
|
-
await _best_effort_checkpointer_call("adelete_thread", thread_id)
|
|
476
|
-
if run_ids:
|
|
477
|
-
await _best_effort_checkpointer_call("adelete_for_runs", list(run_ids))
|
|
478
|
-
await delete_run_stream_events(list(run_ids))
|
|
479
|
-
thread_protocol_broker.delete_thread(thread_id)
|
|
480
|
-
await delete_thread_stream_events(thread_id)
|
|
460
|
+
await delete_threads_cascade([thread_id])
|
|
481
461
|
return Response(status_code=204)
|
|
482
462
|
|
|
483
463
|
|
|
@@ -172,6 +172,7 @@ class DatabaseManager:
|
|
|
172
172
|
self.session_factory = async_sessionmaker(self.engine, expire_on_commit=False)
|
|
173
173
|
async with self.engine.begin() as conn:
|
|
174
174
|
await conn.run_sync(Base.metadata.create_all)
|
|
175
|
+
await conn.run_sync(self._apply_additive_migrations)
|
|
175
176
|
embed_conn_args = {"path": embed_dir, "db_name": settings.OCEANBASE_DB_NAME}
|
|
176
177
|
self._checkpointer = OceanBaseCheckpointSaver(
|
|
177
178
|
connection_args=embed_conn_args
|
|
@@ -195,6 +196,7 @@ class DatabaseManager:
|
|
|
195
196
|
self.session_factory = async_sessionmaker(self.engine, expire_on_commit=False)
|
|
196
197
|
async with self.engine.begin() as conn:
|
|
197
198
|
await conn.run_sync(Base.metadata.create_all)
|
|
199
|
+
await conn.run_sync(self._apply_additive_migrations)
|
|
198
200
|
conn_args = _resolve_connection_args()
|
|
199
201
|
self._checkpointer = OceanBaseCheckpointSaver(
|
|
200
202
|
connection_args=conn_args
|
|
@@ -220,6 +222,51 @@ class DatabaseManager:
|
|
|
220
222
|
await self._setup_langgraph_checkpointer_once()
|
|
221
223
|
await self._setup_store_once()
|
|
222
224
|
|
|
225
|
+
@staticmethod
|
|
226
|
+
def _apply_additive_migrations(connection) -> None:
|
|
227
|
+
"""Add columns introduced after a table was first created.
|
|
228
|
+
|
|
229
|
+
``Base.metadata.create_all`` only creates *missing* tables; it never
|
|
230
|
+
alters an existing one. Deployments whose tables predate a new ORM
|
|
231
|
+
column would therefore be missing it, breaking queries that reference
|
|
232
|
+
it. This inspects each mapped table and issues ``ALTER TABLE ADD
|
|
233
|
+
COLUMN`` for any column present in the model but absent in the DB.
|
|
234
|
+
Idempotent and safe to run on every startup.
|
|
235
|
+
|
|
236
|
+
Invariant for new columns: they must be either nullable or carry a
|
|
237
|
+
``server_default``. ``ADD COLUMN ... NOT NULL`` without a server-side
|
|
238
|
+
default fails on a populated table (there is no value for existing
|
|
239
|
+
rows). Such a column cannot be added automatically here — it requires a
|
|
240
|
+
deliberate data migration — so we skip it with a loud error rather than
|
|
241
|
+
crash startup or emit failing DDL.
|
|
242
|
+
"""
|
|
243
|
+
from sqlalchemy import inspect
|
|
244
|
+
from sqlalchemy.schema import CreateColumn
|
|
245
|
+
|
|
246
|
+
inspector = inspect(connection)
|
|
247
|
+
existing_tables = set(inspector.get_table_names())
|
|
248
|
+
dialect = connection.dialect
|
|
249
|
+
preparer = dialect.identifier_preparer
|
|
250
|
+
for table in Base.metadata.sorted_tables:
|
|
251
|
+
if table.name not in existing_tables:
|
|
252
|
+
continue # create_all already built it with all columns
|
|
253
|
+
db_columns = {col["name"] for col in inspector.get_columns(table.name)}
|
|
254
|
+
for column in table.columns:
|
|
255
|
+
if column.name in db_columns:
|
|
256
|
+
continue
|
|
257
|
+
if not column.nullable and column.server_default is None:
|
|
258
|
+
logger.error(
|
|
259
|
+
"Cannot auto-add NOT NULL column %s.%s without a server_default; "
|
|
260
|
+
"a manual data migration is required. Skipping.",
|
|
261
|
+
table.name,
|
|
262
|
+
column.name,
|
|
263
|
+
)
|
|
264
|
+
continue
|
|
265
|
+
column_spec = CreateColumn(column).compile(dialect=dialect)
|
|
266
|
+
table_name = preparer.format_table(table)
|
|
267
|
+
logger.info("Adding missing column %s.%s", table.name, column.name)
|
|
268
|
+
connection.exec_driver_sql(f"ALTER TABLE {table_name} ADD COLUMN {column_spec}")
|
|
269
|
+
|
|
223
270
|
async def _setup_checkpointer_once(self) -> None:
|
|
224
271
|
if self._checkpointer_setup_done:
|
|
225
272
|
return
|
|
@@ -262,6 +309,14 @@ class DatabaseManager:
|
|
|
262
309
|
self._store_setup_done = True
|
|
263
310
|
|
|
264
311
|
async def close(self) -> None:
|
|
312
|
+
langgraph_checkpointer = self._langgraph_checkpointer
|
|
313
|
+
self._langgraph_checkpointer = None
|
|
314
|
+
close_checkpointer = getattr(langgraph_checkpointer, "close", None)
|
|
315
|
+
if callable(close_checkpointer):
|
|
316
|
+
try:
|
|
317
|
+
await asyncio.to_thread(close_checkpointer)
|
|
318
|
+
except Exception: # noqa: BLE001
|
|
319
|
+
logger.warning("Failed to close LangGraph checkpointer", exc_info=True)
|
|
265
320
|
if self.engine is not None:
|
|
266
321
|
await self.engine.dispose()
|
|
267
322
|
store_engine = getattr(getattr(self._store, "obvector", None), "engine", None)
|
|
@@ -271,7 +326,6 @@ class DatabaseManager:
|
|
|
271
326
|
self.engine = None
|
|
272
327
|
self.session_factory = None
|
|
273
328
|
self._checkpointer = None
|
|
274
|
-
self._langgraph_checkpointer = None
|
|
275
329
|
self._store = None
|
|
276
330
|
self._checkpointer_setup_done = False
|
|
277
331
|
self._langgraph_checkpointer_setup_done = False
|
|
@@ -3,7 +3,7 @@ from typing import Any
|
|
|
3
3
|
from datetime import UTC, datetime
|
|
4
4
|
from uuid import uuid4
|
|
5
5
|
|
|
6
|
-
from sqlalchemy import JSON, Boolean, DateTime, Index, Integer, String, Text, UniqueConstraint
|
|
6
|
+
from sqlalchemy import JSON, Boolean, DateTime, Index, Integer, String, Text, UniqueConstraint, text
|
|
7
7
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
|
8
8
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
|
9
9
|
|
|
@@ -74,6 +74,10 @@ class CronJob(Base):
|
|
|
74
74
|
metadata_json: Mapped[dict] = mapped_column("metadata", JSON, default=dict, nullable=False)
|
|
75
75
|
kwargs_json: Mapped[dict] = mapped_column("kwargs", JSON, default=dict, nullable=False)
|
|
76
76
|
webhook: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
77
|
+
end_time: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
78
|
+
on_run_completed: Mapped[str] = mapped_column(
|
|
79
|
+
String(16), nullable=False, default="delete", server_default=text("'delete'")
|
|
80
|
+
)
|
|
77
81
|
max_webhook_attempts: Mapped[int] = mapped_column(nullable=False, default=3)
|
|
78
82
|
next_run_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
79
83
|
last_run_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|