fix: correct session.chat() return type to match server response

The OpenCode server's POST /session/{id}/message endpoint returns a
wrapper object with 'info' (the assistant message) and 'parts' (the
message parts):

    {"info": AssistantMessage, "parts": Part[]}

The SDK was casting this response to bare AssistantMessage, which
caused Pydantic to silently produce a hollow object where all fields
(id, role, tokens, etc.) were None.

This commit:
- Adds SessionChatResponse type with 'info: Message' and 'parts: List[Part]'
  fields matching the actual server response shape
- Updates both sync and async chat() to return SessionChatResponse
- Exports the new type from types/__init__.py
- Updates api.md documentation
This commit is contained in:
Alberto Garcia Illera 2026-02-21 12:37:30 -08:00
parent 817f1a0816
commit 301f5cec71
No known key found for this signature in database
4 changed files with 28 additions and 5 deletions

2
api.md
View file

@ -122,7 +122,7 @@ Methods:
- <code title="get /session">client.session.<a href="./src/opencode_ai/resources/session.py">list</a>() -> <a href="./src/opencode_ai/types/session_list_response.py">SessionListResponse</a></code>
- <code title="delete /session/{id}">client.session.<a href="./src/opencode_ai/resources/session.py">delete</a>(id) -> <a href="./src/opencode_ai/types/session_delete_response.py">SessionDeleteResponse</a></code>
- <code title="post /session/{id}/abort">client.session.<a href="./src/opencode_ai/resources/session.py">abort</a>(id) -> <a href="./src/opencode_ai/types/session_abort_response.py">SessionAbortResponse</a></code>
- <code title="post /session/{id}/message">client.session.<a href="./src/opencode_ai/resources/session.py">chat</a>(id, \*\*<a href="src/opencode_ai/types/session_chat_params.py">params</a>) -> <a href="./src/opencode_ai/types/assistant_message.py">AssistantMessage</a></code>
- <code title="post /session/{id}/message">client.session.<a href="./src/opencode_ai/resources/session.py">chat</a>(id, \*\*<a href="src/opencode_ai/types/session_chat_params.py">params</a>) -> <a href="./src/opencode_ai/types/session_chat_response.py">SessionChatResponse</a></code>
- <code title="post /session/{id}/init">client.session.<a href="./src/opencode_ai/resources/session.py">init</a>(id, \*\*<a href="src/opencode_ai/types/session_init_params.py">params</a>) -> <a href="./src/opencode_ai/types/session_init_response.py">SessionInitResponse</a></code>
- <code title="get /session/{id}/message">client.session.<a href="./src/opencode_ai/resources/session.py">messages</a>(id) -> <a href="./src/opencode_ai/types/session_messages_response.py">SessionMessagesResponse</a></code>
- <code title="post /session/{id}/revert">client.session.<a href="./src/opencode_ai/resources/session.py">revert</a>(id, \*\*<a href="src/opencode_ai/types/session_revert_params.py">params</a>) -> <a href="./src/opencode_ai/types/session.py">Session</a></code>

View file

@ -23,6 +23,7 @@ from ..types.assistant_message import AssistantMessage
from ..types.session_init_response import SessionInitResponse
from ..types.session_list_response import SessionListResponse
from ..types.session_abort_response import SessionAbortResponse
from ..types.session_chat_response import SessionChatResponse
from ..types.session_delete_response import SessionDeleteResponse
from ..types.session_messages_response import SessionMessagesResponse
from ..types.session_summarize_response import SessionSummarizeResponse
@ -171,7 +172,7 @@ class SessionResource(SyncAPIResource):
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
) -> AssistantMessage:
) -> SessionChatResponse:
"""
Create and send a new message to a session
@ -205,7 +206,7 @@ class SessionResource(SyncAPIResource):
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=AssistantMessage,
cast_to=SessionChatResponse,
)
def init(
@ -616,7 +617,7 @@ class AsyncSessionResource(AsyncAPIResource):
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
) -> AssistantMessage:
) -> SessionChatResponse:
"""
Create and send a new message to a session
@ -650,7 +651,7 @@ class AsyncSessionResource(AsyncAPIResource):
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=AssistantMessage,
cast_to=SessionChatResponse,
)
async def init(

View file

@ -49,6 +49,7 @@ from .event_list_response import EventListResponse as EventListResponse
from .find_files_response import FindFilesResponse as FindFilesResponse
from .find_symbols_params import FindSymbolsParams as FindSymbolsParams
from .session_chat_params import SessionChatParams as SessionChatParams
from .session_chat_response import SessionChatResponse as SessionChatResponse
from .session_init_params import SessionInitParams as SessionInitParams
from .symbol_source_param import SymbolSourceParam as SymbolSourceParam
from .file_status_response import FileStatusResponse as FileStatusResponse

View file

@ -0,0 +1,21 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
from typing import List
from typing_extensions import TypeAlias
from .part import Part
from .message import Message
from .._models import BaseModel
__all__ = ["SessionChatResponse"]
class SessionChatResponse(BaseModel):
"""Response from POST /session/{id}/message.
Contains the assistant message metadata and its parts (text, tool calls, etc.).
"""
info: Message
parts: List[Part]