diff --git a/docs/en/api/05-sessions.md b/docs/en/api/05-sessions.md index 8ce60c0d3..17050f5df 100644 --- a/docs/en/api/05-sessions.md +++ b/docs/en/api/05-sessions.md @@ -20,6 +20,10 @@ Create a new session. # Create new session (auto-generated ID) session = client.session() print(f"Session URI: {session.uri}") + +# Create new session with specified ID +session = client.create_session(session_id="my-custom-session-id") +print(f"Session ID: {session['session_id']}") ``` **HTTP API** @@ -29,9 +33,16 @@ POST /api/v1/sessions ``` ```bash +# Create new session (auto-generated ID) curl -X POST http://localhost:1933/api/v1/sessions \ -H "Content-Type: application/json" \ -H "X-API-Key: your-key" + +# Create new session with specified ID +curl -X POST http://localhost:1933/api/v1/sessions \ + -H "Content-Type: application/json" \ + -H "X-API-Key: your-key" \ + -d '{"session_id": "my-custom-session-id"}' ``` **CLI** diff --git a/docs/zh/api/05-sessions.md b/docs/zh/api/05-sessions.md index 4e294eb09..7ba869c8a 100644 --- a/docs/zh/api/05-sessions.md +++ b/docs/zh/api/05-sessions.md @@ -20,6 +20,10 @@ # 创建新会话(自动生成 ID) session = client.session() print(f"Session URI: {session.uri}") + +# 创建指定 ID 的新会话 +session = client.create_session(session_id="my-custom-session-id") +print(f"Session ID: {session['session_id']}") ``` **HTTP API** @@ -29,9 +33,16 @@ POST /api/v1/sessions ``` ```bash +# 创建新会话(自动生成 ID) curl -X POST http://localhost:1933/api/v1/sessions \ -H "Content-Type: application/json" \ -H "X-API-Key: your-key" + +# 创建指定 ID 的新会话 +curl -X POST http://localhost:1933/api/v1/sessions \ + -H "Content-Type: application/json" \ + -H "X-API-Key: your-key" \ + -d '{"session_id": "my-custom-session-id"}' ``` **CLI** diff --git a/openviking/async_client.py b/openviking/async_client.py index 98b4c0f17..cb3c50083 100644 --- a/openviking/async_client.py +++ b/openviking/async_client.py @@ -128,10 +128,15 @@ async def session_exists(self, session_id: str) -> bool: await self._ensure_initialized() return await self._client.session_exists(session_id) - async def create_session(self) -> Dict[str, Any]: - """Create a new session.""" + async def create_session(self, session_id: Optional[str] = None) -> Dict[str, Any]: + """Create a new session. + + Args: + session_id: Optional session ID. If provided, creates a session with the given ID. + If None, creates a new session with auto-generated ID. + """ await self._ensure_initialized() - return await self._client.create_session() + return await self._client.create_session(session_id) async def list_sessions(self) -> List[Any]: """List all sessions.""" diff --git a/openviking/client/local.py b/openviking/client/local.py index 85215c1ff..4737fb604 100644 --- a/openviking/client/local.py +++ b/openviking/client/local.py @@ -319,11 +319,16 @@ async def unlink(self, from_uri: str, to_uri: str) -> None: # ============= Sessions ============= - async def create_session(self) -> Dict[str, Any]: - """Create a new session.""" + async def create_session(self, session_id: Optional[str] = None) -> Dict[str, Any]: + """Create a new session. + + Args: + session_id: Optional session ID. If provided, creates a session with the given ID. + If None, creates a new session with auto-generated ID. + """ await self._service.initialize_user_directories(self._ctx) await self._service.initialize_agent_directories(self._ctx) - session = await self._service.sessions.create(self._ctx) + session = await self._service.sessions.create(self._ctx, session_id) return { "session_id": session.session_id, "user": session.user.to_dict(), diff --git a/openviking/server/routers/sessions.py b/openviking/server/routers/sessions.py index 0977685bf..13ddbf4ad 100644 --- a/openviking/server/routers/sessions.py +++ b/openviking/server/routers/sessions.py @@ -78,6 +78,12 @@ class UsedRequest(BaseModel): skill: Optional[Dict[str, Any]] = None +class CreateSessionRequest(BaseModel): + """Request model for creating a session.""" + + session_id: Optional[str] = None + + def _to_jsonable(value: Any) -> Any: """Convert internal objects (e.g. Context) into JSON-serializable values.""" to_dict = getattr(value, "to_dict", None) @@ -92,13 +98,19 @@ def _to_jsonable(value: Any) -> Any: @router.post("") async def create_session( + request: Optional[CreateSessionRequest] = None, _ctx: RequestContext = Depends(get_request_context), ): - """Create a new session.""" + """Create a new session. + + If session_id is provided, creates a session with the given ID. + If session_id is None, creates a new session with auto-generated ID. + """ service = get_service() await service.initialize_user_directories(_ctx) await service.initialize_agent_directories(_ctx) - session = await service.sessions.create(_ctx) + session_id = request.session_id if request else None + session = await service.sessions.create(_ctx, session_id) return Response( status="ok", result={ diff --git a/openviking/service/session_service.py b/openviking/service/session_service.py index 0e23e10ac..95cea313e 100644 --- a/openviking/service/session_service.py +++ b/openviking/service/session_service.py @@ -14,7 +14,7 @@ from openviking.session.compressor import SessionCompressor from openviking.storage import VikingDBManager from openviking.storage.viking_fs import VikingFS -from openviking_cli.exceptions import NotFoundError, NotInitializedError +from openviking_cli.exceptions import AlreadyExistsError, NotFoundError, NotInitializedError from openviking_cli.utils import get_logger logger = get_logger(__name__) @@ -68,9 +68,22 @@ def session(self, ctx: RequestContext, session_id: Optional[str] = None) -> Sess session_id=session_id, ) - async def create(self, ctx: RequestContext) -> Session: - """Create a session and persist its root path.""" - session = self.session(ctx) + async def create(self, ctx: RequestContext, session_id: Optional[str] = None) -> Session: + """Create a session and persist its root path. + + Args: + ctx: Request context + session_id: Optional session ID. If provided, creates a session with the given ID. + If None, creates a new session with auto-generated ID. + + Raises: + AlreadyExistsError: If a session with the given ID already exists + """ + if session_id: + existing = self.session(ctx, session_id) + if await existing.exists(): + raise AlreadyExistsError(f"Session '{session_id}' already exists") + session = self.session(ctx, session_id) await session.ensure_exists() return session diff --git a/openviking/sync_client.py b/openviking/sync_client.py index d6c333c1c..f2e885dfb 100644 --- a/openviking/sync_client.py +++ b/openviking/sync_client.py @@ -39,9 +39,14 @@ def session_exists(self, session_id: str) -> bool: """Check whether a session exists in storage.""" return run_async(self._async_client.session_exists(session_id)) - def create_session(self) -> Dict[str, Any]: - """Create a new session.""" - return run_async(self._async_client.create_session()) + def create_session(self, session_id: Optional[str] = None) -> Dict[str, Any]: + """Create a new session. + + Args: + session_id: Optional session ID. If provided, creates a session with the given ID. + If None, creates a new session with auto-generated ID. + """ + return run_async(self._async_client.create_session(session_id)) def list_sessions(self) -> List[Any]: """List all sessions.""" diff --git a/openviking_cli/client/base.py b/openviking_cli/client/base.py index 30fe8febe..5e9f362c5 100644 --- a/openviking_cli/client/base.py +++ b/openviking_cli/client/base.py @@ -192,8 +192,13 @@ async def unlink(self, from_uri: str, to_uri: str) -> None: # ============= Sessions ============= @abstractmethod - async def create_session(self) -> Dict[str, Any]: - """Create a new session.""" + async def create_session(self, session_id: Optional[str] = None) -> Dict[str, Any]: + """Create a new session. + + Args: + session_id: Optional session ID. If provided, creates a session with the given ID. + If None, creates a new session with auto-generated ID. + """ ... @abstractmethod diff --git a/openviking_cli/client/http.py b/openviking_cli/client/http.py index 0b1689ad8..5fe3d678f 100644 --- a/openviking_cli/client/http.py +++ b/openviking_cli/client/http.py @@ -684,11 +684,17 @@ async def unlink(self, from_uri: str, to_uri: str) -> None: # ============= Sessions ============= - async def create_session(self) -> Dict[str, Any]: - """Create a new session.""" + async def create_session(self, session_id: Optional[str] = None) -> Dict[str, Any]: + """Create a new session. + + Args: + session_id: Optional session ID. If provided, creates a session with the given ID. + If None, creates a new session with auto-generated ID. + """ + json_body = {"session_id": session_id} if session_id else {} response = await self._http.post( "/api/v1/sessions", - json={}, + json=json_body, ) return self._handle_response(response) diff --git a/openviking_cli/client/sync_http.py b/openviking_cli/client/sync_http.py index fba30a372..2bc9da532 100644 --- a/openviking_cli/client/sync_http.py +++ b/openviking_cli/client/sync_http.py @@ -78,9 +78,14 @@ def session_exists(self, session_id: str) -> bool: """Check whether a session exists in storage.""" return run_async(self._async_client.session_exists(session_id)) - def create_session(self) -> Dict[str, Any]: - """Create a new session.""" - return run_async(self._async_client.create_session()) + def create_session(self, session_id: Optional[str] = None) -> Dict[str, Any]: + """Create a new session. + + Args: + session_id: Optional session ID. If provided, creates a session with the given ID. + If None, creates a new session with auto-generated ID. + """ + return run_async(self._async_client.create_session(session_id)) def list_sessions(self) -> List[Any]: """List all sessions.""" diff --git a/tests/api_test/sessions/test_create_session.py b/tests/api_test/sessions/test_create_session.py index 2aee03070..dd24ca48b 100644 --- a/tests/api_test/sessions/test_create_session.py +++ b/tests/api_test/sessions/test_create_session.py @@ -29,3 +29,40 @@ def test_create_session(self, api_client): assert data["result"] is not None, "'result' should not be null" assert "session_id" in data["result"], "'session_id' field should exist" assert "user" in data["result"], "'user' field should exist" + + def test_create_session_with_custom_id(self, api_client): + custom_session_id = "test-custom-session-12345" + try: + response = api_client.create_session(session_id=custom_session_id) + except requests.exceptions.ConnectionError: + pytest.fail("Could not connect to server service - service is not running") + + assert response.status_code < 500, ( + f"Create session with custom ID failed with status {response.status_code}" + ) + + if response.status_code == 200: + data = response.json() + print("\n" + "=" * 80) + print("Create Session with Custom ID Response:") + print("=" * 80) + print(json.dumps(data, indent=2, ensure_ascii=False)) + print("=" * 80 + "\n") + + assert data.get("status") == "ok", f"Expected status 'ok', got {data.get('status')}" + assert data.get("error") is None, f"Expected error to be null, got {data.get('error')}" + assert "result" in data, "'result' field should exist" + assert data["result"]["session_id"] == custom_session_id, ( + f"Expected session_id '{custom_session_id}', got {data['result']['session_id']}" + ) + assert "user" in data["result"], "'user' field should exist" + + get_response = api_client.get_session(custom_session_id) + assert get_response.status_code == 200, ( + f"Get session failed with status {get_response.status_code}" + ) + + get_data = get_response.json() + assert get_data["result"]["session_id"] == custom_session_id, ( + "Retrieved session_id does not match created custom session_id" + )