fix(sdk): add peer field to session creation methods (#705)

This commit is contained in:
Vineeth Voruganti 2026-05-21 12:31:53 -04:00 committed by GitHub
parent b5f24a6ac5
commit 10f72a7d0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 135 additions and 4 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)
})
})
// ===========================================================================

View File

@ -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<string, unknown>
configuration?: SessionConfig
peers?: Record<
string,
{ observe_me?: boolean | null; observe_others?: boolean | null }
>
}
): Promise<SessionResponse> {
return this._http.post<SessionResponse>(
@ -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<Session> {
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,

View File

@ -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]):
"""