From 10f72a7d0d937fc0b5419270142d88f5b1f3ae90 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 21 May 2026 12:31:53 -0400 Subject: [PATCH] fix(sdk): add peer field to session creation methods (#705) --- .pre-commit-config.yaml | 2 +- sdks/python/src/honcho/aio.py | 13 ++++++ sdks/python/src/honcho/client.py | 19 ++++++++- sdks/typescript/__tests__/session.test.ts | 36 +++++++++++++++++ sdks/typescript/src/client.ts | 20 ++++++++- tests/sdk/test_session.py | 49 +++++++++++++++++++++++ 6 files changed, 135 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4ba8ab43..c093bf34 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -60,7 +60,7 @@ repos: language: system files: ^(src/|tests/|sdks/python/|scripts/).*\.py$ require_serial: true - pass_filenames: false + pass_filenames: true # Run main application tests - id: pytest-main diff --git a/sdks/python/src/honcho/aio.py b/sdks/python/src/honcho/aio.py index 0edcc60f..04ca42fb 100644 --- a/sdks/python/src/honcho/aio.py +++ b/sdks/python/src/honcho/aio.py @@ -243,6 +243,14 @@ class HonchoAio(AsyncMetadataConfigMixin): *, metadata: dict[str, object] | None = None, configuration: SessionConfiguration | None = None, + peers: str + | PeerBase + | tuple[str, SessionPeerConfig] + | tuple[PeerBase, SessionPeerConfig] + | list[PeerBase | str] + | list[tuple[PeerBase | str, SessionPeerConfig]] + | list[PeerBase | str | tuple[PeerBase | str, SessionPeerConfig]] + | None = None, ) -> Session: """ Get or create a session with the given ID asynchronously. @@ -251,6 +259,9 @@ class HonchoAio(AsyncMetadataConfigMixin): id: Unique identifier for the session within the workspace. metadata: Optional metadata dictionary to associate with this session. configuration: Optional configuration to set for this session. + peers: Optional peers to attach to the session at creation. Accepts the + same shape as Session.add_peers (peer ID string, Peer object, list + of either, or tuples with SessionPeerConfig). Returns: A Session object with cached values from the API response. @@ -261,6 +272,8 @@ class HonchoAio(AsyncMetadataConfigMixin): body["metadata"] = metadata if configuration is not None: body["configuration"] = configuration.model_dump(exclude_none=True) + if peers is not None: + body["peers"] = normalize_peers_to_dict(peers) data = await self._honcho._async_http_client.post( routes.sessions(self._honcho.workspace_id), body=body diff --git a/sdks/python/src/honcho/client.py b/sdks/python/src/honcho/client.py index 969ff741..1dbd3a8c 100644 --- a/sdks/python/src/honcho/client.py +++ b/sdks/python/src/honcho/client.py @@ -17,6 +17,7 @@ from .api_types import ( PeerResponse, QueueStatusResponse, SessionConfiguration, + SessionPeerConfig, SessionResponse, WorkspaceConfiguration, WorkspaceResponse, @@ -28,7 +29,7 @@ from .mixins import MetadataConfigMixin from .pagination import SyncPage from .peer import Peer from .session import Session -from .utils import resolve_id +from .utils import normalize_peers_to_dict, resolve_id logger = logging.getLogger(__name__) @@ -400,6 +401,17 @@ class Honcho(BaseModel, MetadataConfigMixin): # pyright: ignore[reportUnsafeMul None, description="Optional configuration to set for this session. If set, will get/create session immediately with flags.", ), + peers: str + | PeerBase + | tuple[str, SessionPeerConfig] + | tuple[PeerBase, SessionPeerConfig] + | list[PeerBase | str] + | list[tuple[PeerBase | str, SessionPeerConfig]] + | list[PeerBase | str | tuple[PeerBase | str, SessionPeerConfig]] + | None = Field( + None, + description="Optional peers to attach to the session at creation. Accepts the same shape as Session.add_peers.", + ), ) -> Session: """ Get or create a session with the given ID. @@ -411,6 +423,9 @@ class Honcho(BaseModel, MetadataConfigMixin): # pyright: ignore[reportUnsafeMul id: Unique identifier for the session within the workspace. metadata: Optional metadata dictionary to associate with this session. configuration: Optional configuration to set for this session. + peers: Optional peers to attach to the session at creation. Accepts the + same shape as Session.add_peers (peer ID string, Peer object, list + of either, or tuples with SessionPeerConfig). Returns: A Session object with cached metadata, configuration, created_at, and is_active. @@ -421,6 +436,8 @@ class Honcho(BaseModel, MetadataConfigMixin): # pyright: ignore[reportUnsafeMul body["metadata"] = metadata if configuration is not None: body["configuration"] = configuration.model_dump(exclude_none=True) + if peers is not None: + body["peers"] = normalize_peers_to_dict(peers) data = self._http.post(routes.sessions(self.workspace_id), body=body) session_data = SessionResponse.model_validate(data) diff --git a/sdks/typescript/__tests__/session.test.ts b/sdks/typescript/__tests__/session.test.ts index b9f4cdeb..f7f2f838 100644 --- a/sdks/typescript/__tests__/session.test.ts +++ b/sdks/typescript/__tests__/session.test.ts @@ -95,6 +95,42 @@ describe('Session', () => { expect(session1.id).toBe(session2.id) expect(session2.metadata).toEqual({ version: 2 }) }) + + test('creates session with peers from string array', async () => { + const session = await client.session('session-with-peers-strings', { + peers: ['create-peer-a', 'create-peer-b'], + }) + + const peers = await session.peers() + const ids = peers.map((p) => p.id) + expect(ids).toContain('create-peer-a') + expect(ids).toContain('create-peer-b') + }) + + test('creates session with peers from Peer objects', async () => { + const peerA = await client.peer('create-obj-peer-a') + const peerB = await client.peer('create-obj-peer-b') + const session = await client.session('session-with-peer-objects', { + peers: [peerA, peerB], + }) + + const peers = await session.peers() + const ids = peers.map((p) => p.id) + expect(ids).toContain('create-obj-peer-a') + expect(ids).toContain('create-obj-peer-b') + }) + + test('creates session with peers and per-peer config', async () => { + const session = await client.session('session-with-peer-config', { + peers: [ + ['create-config-peer', { observeMe: true, observeOthers: false }], + ], + }) + + const config = await session.getPeerConfiguration('create-config-peer') + expect(config.observeMe).toBe(true) + expect(config.observeOthers).toBe(false) + }) }) // =========================================================================== diff --git a/sdks/typescript/src/client.ts b/sdks/typescript/src/client.ts index 1270a319..4b510945 100644 --- a/sdks/typescript/src/client.ts +++ b/sdks/typescript/src/client.ts @@ -22,6 +22,8 @@ import { HonchoConfigSchema, LimitSchema, normalizeListOptions, + type PeerAddition, + PeerAdditionToApiSchema, type PeerConfig, PeerConfigSchema, PeerIdSchema, @@ -338,6 +340,10 @@ export class Honcho { id: string metadata?: Record configuration?: SessionConfig + peers?: Record< + string, + { observe_me?: boolean | null; observe_others?: boolean | null } + > } ): Promise { return this._http.post( @@ -347,6 +353,7 @@ export class Honcho { id: params.id, metadata: params.metadata, configuration: sessionConfigToApi(params.configuration), + peers: params.peers, }, } ) @@ -488,10 +495,13 @@ export class Honcho { * @param id - Unique identifier for the session within the workspace. Should be a * stable identifier that can be used consistently to reference the * same conversation - * @param metadata - Optional metadata dictionary to associate with this session. + * @param options.metadata - Optional metadata dictionary to associate with this session. * If set, will get/create session immediately with metadata. - * @param configuration - Optional configuration to set for this session. + * @param options.configuration - Optional configuration to set for this session. * If set, will get/create session immediately with flags. + * @param options.peers - Optional peers to attach to the session at creation. + * Accepts the same shape as `session.addPeers()` (peer ID strings, + * Peer objects, arrays of either, or a record with per-peer config). * @returns Promise resolving to a Session object that can be used to add peers, * send messages, and manage conversation context * @throws Error if the session ID is empty or invalid @@ -501,6 +511,7 @@ export class Honcho { options?: { metadata?: SessionMetadata configuration?: SessionConfig + peers?: PeerAddition } ): Promise { await this._ensureWorkspace() @@ -511,11 +522,16 @@ export class Honcho { const validatedConfiguration = options?.configuration ? SessionConfigSchema.parse(options.configuration) : undefined + const validatedPeers = + options?.peers !== undefined + ? PeerAdditionToApiSchema.parse(options.peers) + : undefined const sessionData = await this._getOrCreateSession(this.workspaceId, { id: validatedId, configuration: validatedConfiguration, metadata: validatedMetadata, + peers: validatedPeers, }) return new Session( validatedId, diff --git a/tests/sdk/test_session.py b/tests/sdk/test_session.py index 3a618891..a15693fa 100644 --- a/tests/sdk/test_session.py +++ b/tests/sdk/test_session.py @@ -200,6 +200,55 @@ async def test_session_peer_config(client_fixture: tuple[Honcho, str]): assert retrieved_config.observe_others +@pytest.mark.asyncio +async def test_session_create_with_peers(client_fixture: tuple[Honcho, str]): + """ + Tests creating a session with peers attached in a single call. + """ + honcho_client, client_type = client_fixture + + if client_type == "async": + session = await honcho_client.aio.session( + id="test-session-create-peers-async", + peers=["create-peer-async-a", "create-peer-async-b"], + ) + assert isinstance(session, Session) + peers = await session.aio.peers() + peer_ids = {p.id for p in peers} + assert "create-peer-async-a" in peer_ids + assert "create-peer-async-b" in peer_ids + + config = SessionPeerConfig(observe_me=True, observe_others=False) + peer = await honcho_client.aio.peer(id="create-peer-async-config") + session_with_config = await honcho_client.aio.session( + id="test-session-create-peers-config-async", + peers=[(peer, config)], + ) + retrieved = await session_with_config.aio.get_peer_configuration(peer) + assert retrieved.observe_me is True + assert retrieved.observe_others is False + else: + session = honcho_client.session( + id="test-session-create-peers", + peers=["create-peer-a", "create-peer-b"], + ) + assert isinstance(session, Session) + peers = session.peers() + peer_ids = {p.id for p in peers} + assert "create-peer-a" in peer_ids + assert "create-peer-b" in peer_ids + + config = SessionPeerConfig(observe_me=True, observe_others=False) + peer = honcho_client.peer(id="create-peer-config") + session_with_config = honcho_client.session( + id="test-session-create-peers-config", + peers=[(peer, config)], + ) + retrieved = session_with_config.get_peer_configuration(peer) + assert retrieved.observe_me is True + assert retrieved.observe_others is False + + @pytest.mark.asyncio async def test_session_messages(client_fixture: tuple[Honcho, str]): """