diff --git a/openapi.json b/openapi.json index 7f1bf94..79b3733 100644 --- a/openapi.json +++ b/openapi.json @@ -4186,6 +4186,183 @@ } } }, + "/api/v2/sessions/{session_id}/files/list_files": { + "post": { + "tags": [ + "Session Files" + ], + "summary": "List Files", + "description": "List files on the session's pod (allowlisted roots only), newest first, paged via the cursor.", + "operationId": "list_files_api_v2_sessions__session_id__files_list_files_post", + "security": [ + { + "HTTPBearer": [] + } + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListFilesRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListFilesResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v2/sessions/{session_id}/files/read_file": { + "post": { + "tags": [ + "Session Files" + ], + "summary": "Read File", + "description": "Read a file from the session's pod, returned base64-encoded (size-capped).", + "operationId": "read_file_api_v2_sessions__session_id__files_read_file_post", + "security": [ + { + "HTTPBearer": [] + } + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReadFileRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReadFileResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v2/sessions/{session_id}/files/write_file": { + "post": { + "tags": [ + "Session Files" + ], + "summary": "Write File", + "description": "Write base64-encoded content to a file on the session's pod (size-capped).", + "operationId": "write_file_api_v2_sessions__session_id__files_write_file_post", + "security": [ + { + "HTTPBearer": [] + } + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WriteFileRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WriteFileResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, "/api/v2/quota/tokens": { "get": { "tags": [ @@ -5383,6 +5560,41 @@ "title": "Feedback", "description": "Feedback on the semantic success of the trajectory." }, + "FileEntry": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "path": { + "type": "string", + "title": "Path" + }, + "size_bytes": { + "type": "integer", + "title": "Size Bytes" + }, + "modified_at": { + "type": "string", + "format": "date-time", + "title": "Modified At" + }, + "is_dir": { + "type": "boolean", + "title": "Is Dir" + } + }, + "type": "object", + "required": [ + "name", + "path", + "size_bytes", + "modified_at", + "is_dir" + ], + "title": "FileEntry", + "description": "One entry in a directory listing." + }, "FlowEvent": { "properties": { "kind": { @@ -5493,6 +5705,74 @@ "description": "Response model for initiating a browser profile upload." }, "JsonValue": {}, + "ListFilesRequest": { + "properties": { + "path": { + "type": "string", + "title": "Path", + "default": "~/Downloads" + }, + "max_entries": { + "type": "integer", + "minimum": 1.0, + "title": "Max Entries", + "default": 500 + }, + "modified_before": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Modified Before" + }, + "name_after": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name After" + } + }, + "type": "object", + "title": "ListFilesRequest", + "description": "Directory to list on the session's pod." + }, + "ListFilesResponse": { + "properties": { + "path": { + "type": "string", + "title": "Path" + }, + "files": { + "items": { + "$ref": "#/components/schemas/FileEntry" + }, + "type": "array", + "title": "Files" + }, + "truncated": { + "type": "boolean", + "title": "Truncated" + } + }, + "type": "object", + "required": [ + "path", + "files", + "truncated" + ], + "title": "ListFilesResponse", + "description": "Directory listing, newest first; ``truncated`` when capped at ``max_entries``." + }, "LiveViewUrlData": { "properties": { "live_view_url": { @@ -6509,6 +6789,50 @@ "title": "QuotaStatus", "description": "Quota status." }, + "ReadFileRequest": { + "properties": { + "path": { + "type": "string", + "title": "Path" + } + }, + "type": "object", + "required": [ + "path" + ], + "title": "ReadFileRequest", + "description": "File to read from the session's pod." + }, + "ReadFileResponse": { + "properties": { + "path": { + "type": "string", + "title": "Path" + }, + "content_base64": { + "type": "string", + "title": "Content Base64" + }, + "size_bytes": { + "type": "integer", + "title": "Size Bytes" + }, + "modified_at": { + "type": "string", + "format": "date-time", + "title": "Modified At" + } + }, + "type": "object", + "required": [ + "path", + "content_base64", + "size_bytes", + "modified_at" + ], + "title": "ReadFileResponse", + "description": "A file's base64 contents and metadata." + }, "RequestStartData": { "properties": {}, "additionalProperties": true, @@ -7269,6 +7593,19 @@ ], "title": "Finished At" }, + "last_activity_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Last Activity At", + "description": "When the session's latest event was ingested (agent steps, status changes, user messages). Null for sessions that predate this field." + }, "anonymized_at": { "anyOf": [ { @@ -8240,6 +8577,44 @@ ], "title": "WebhookWithSecret", "description": "Create response: the only time the signing secret is returned." + }, + "WriteFileRequest": { + "properties": { + "path": { + "type": "string", + "title": "Path" + }, + "content_base64": { + "type": "string", + "title": "Content Base64" + } + }, + "type": "object", + "required": [ + "path", + "content_base64" + ], + "title": "WriteFileRequest", + "description": "File to write on the session's pod, base64-encoded." + }, + "WriteFileResponse": { + "properties": { + "path": { + "type": "string", + "title": "Path" + }, + "size_bytes": { + "type": "integer", + "title": "Size Bytes" + } + }, + "type": "object", + "required": [ + "path", + "size_bytes" + ], + "title": "WriteFileResponse", + "description": "The written file's resolved path and byte count." } }, "securitySchemes": { diff --git a/pyproject.toml b/pyproject.toml index 5f234f7..bdc87f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "hai-agents" -version = "1.0.7" +version = "1.0.8" description = "Python SDK for H Company's Computer-Use Agents: autonomous agents powered by Holo." requires-python = ">=3.10" readme = "README.md" diff --git a/src/hai_agents/__init__.py b/src/hai_agents/__init__.py index 3caa0a9..11201e8 100644 --- a/src/hai_agents/__init__.py +++ b/src/hai_agents/__init__.py @@ -58,12 +58,14 @@ ErrorEvent, ErrorEventKind, Feedback, + FileEntry, FlowEvent, HttpValidationError, ImageContent, ImageContentType, InitiateUploadResponse, JsonValue, + ListFilesResponse, LiveViewUrlData, LiveViewUrlEvent, ManagedProxySelection, @@ -89,6 +91,7 @@ ProxyPool, QuotaStatus, QuotaStatusScope, + ReadFileResponse, RequestStartData, RequestStartDispatchedData, RequestStartDispatchedEvent, @@ -145,9 +148,21 @@ WebhookRecordLastDeliveryStatus, WebhookWithSecret, WebhookWithSecretLastDeliveryStatus, + WriteFileResponse, ) from .errors import NotFoundError, UnprocessableEntityError - from . import agents, browser_profiles, environments, quota, schedules, sessions, skills, vaults, webhooks + from . import ( + agents, + browser_profiles, + environments, + quota, + schedules, + session_files, + sessions, + skills, + vaults, + webhooks, + ) from ._default_clients import DefaultAioHttpClient, DefaultAsyncHttpxClient from .agents import ( ListAgentsRequestSortItem, @@ -263,6 +278,7 @@ "ErrorEvent": ".types", "ErrorEventKind": ".types", "Feedback": ".types", + "FileEntry": ".types", "FlowEvent": ".types", "HaiAgentsEnvironment": ".environment", "HttpValidationError": ".types", @@ -272,6 +288,7 @@ "JsonValue": ".types", "ListAgentsRequestSortItem": ".agents", "ListEnvironmentsRequestSortItem": ".environments", + "ListFilesResponse": ".types", "ListScheduleRunsRequestSortItem": ".schedules", "ListSchedulesRequestSortItem": ".schedules", "ListSessionEventsRequestSortItem": ".sessions", @@ -313,6 +330,7 @@ "ProxyPool": ".types", "QuotaStatus": ".types", "QuotaStatusScope": ".types", + "ReadFileResponse": ".types", "RequestStartData": ".types", "RequestStartDispatchedData": ".types", "RequestStartDispatchedEvent": ".types", @@ -385,6 +403,7 @@ "WebhookVerificationError": ".webhook_verification", "WebhookWithSecret": ".types", "WebhookWithSecretLastDeliveryStatus": ".types", + "WriteFileResponse": ".types", "agents": ".agents", "as_tools": ".tools", "assert_request_under_limit": ".polling", @@ -398,6 +417,7 @@ "quota": ".quota", "run_session": ".polling", "schedules": ".schedules", + "session_files": ".session_files", "sessions": ".sessions", "skills": ".skills", "stream_session": ".polling", @@ -491,6 +511,7 @@ def __dir__(): "ErrorEvent", "ErrorEventKind", "Feedback", + "FileEntry", "FlowEvent", "HaiAgentsEnvironment", "HttpValidationError", @@ -500,6 +521,7 @@ def __dir__(): "JsonValue", "ListAgentsRequestSortItem", "ListEnvironmentsRequestSortItem", + "ListFilesResponse", "ListScheduleRunsRequestSortItem", "ListSchedulesRequestSortItem", "ListSessionEventsRequestSortItem", @@ -541,6 +563,7 @@ def __dir__(): "ProxyPool", "QuotaStatus", "QuotaStatusScope", + "ReadFileResponse", "RequestStartData", "RequestStartDispatchedData", "RequestStartDispatchedEvent", @@ -613,6 +636,7 @@ def __dir__(): "WebhookVerificationError", "WebhookWithSecret", "WebhookWithSecretLastDeliveryStatus", + "WriteFileResponse", "agents", "as_tools", "assert_request_under_limit", @@ -626,6 +650,7 @@ def __dir__(): "quota", "run_session", "schedules", + "session_files", "sessions", "skills", "stream_session", diff --git a/src/hai_agents/base_client.py b/src/hai_agents/base_client.py index 5ec46d2..d599c0d 100644 --- a/src/hai_agents/base_client.py +++ b/src/hai_agents/base_client.py @@ -17,6 +17,7 @@ from .environments.client import AsyncEnvironmentsClient, EnvironmentsClient from .quota.client import AsyncQuotaClient, QuotaClient from .schedules.client import AsyncSchedulesClient, SchedulesClient + from .session_files.client import AsyncSessionFilesClient, SessionFilesClient from .sessions.client import AsyncSessionsClient, SessionsClient from .skills.client import AsyncSkillsClient, SkillsClient from .vaults.client import AsyncVaultsClient, VaultsClient @@ -111,6 +112,7 @@ def __init__( self._schedules: typing.Optional[SchedulesClient] = None self._browser_profiles: typing.Optional[BrowserProfilesClient] = None self._vaults: typing.Optional[VaultsClient] = None + self._session_files: typing.Optional[SessionFilesClient] = None self._quota: typing.Optional[QuotaClient] = None @property @@ -177,6 +179,14 @@ def vaults(self): self._vaults = VaultsClient(client_wrapper=self._client_wrapper) return self._vaults + @property + def session_files(self): + if self._session_files is None: + from .session_files.client import SessionFilesClient # noqa: E402 + + self._session_files = SessionFilesClient(client_wrapper=self._client_wrapper) + return self._session_files + @property def quota(self): if self._quota is None: @@ -295,6 +305,7 @@ def __init__( self._schedules: typing.Optional[AsyncSchedulesClient] = None self._browser_profiles: typing.Optional[AsyncBrowserProfilesClient] = None self._vaults: typing.Optional[AsyncVaultsClient] = None + self._session_files: typing.Optional[AsyncSessionFilesClient] = None self._quota: typing.Optional[AsyncQuotaClient] = None @property @@ -361,6 +372,14 @@ def vaults(self): self._vaults = AsyncVaultsClient(client_wrapper=self._client_wrapper) return self._vaults + @property + def session_files(self): + if self._session_files is None: + from .session_files.client import AsyncSessionFilesClient # noqa: E402 + + self._session_files = AsyncSessionFilesClient(client_wrapper=self._client_wrapper) + return self._session_files + @property def quota(self): if self._quota is None: diff --git a/src/hai_agents/core/client_wrapper.py b/src/hai_agents/core/client_wrapper.py index 0100d15..3d2b9b7 100644 --- a/src/hai_agents/core/client_wrapper.py +++ b/src/hai_agents/core/client_wrapper.py @@ -29,9 +29,9 @@ def get_headers(self) -> typing.Dict[str, str]: import platform headers: typing.Dict[str, str] = { - "User-Agent": "hai_agents/1.0.7", + "User-Agent": "hai_agents/1.0.8", "X-HCompany-Client-Name": "hai_agents", - "X-HCompany-Client-Version": "1.0.7", + "X-HCompany-Client-Version": "1.0.8", "X-HCompany-Client-Type": "sdk", "X-HCompany-Language": "Python", "X-HCompany-Runtime": f"python/{platform.python_version()}", diff --git a/src/hai_agents/session_files/__init__.py b/src/hai_agents/session_files/__init__.py new file mode 100644 index 0000000..5cde020 --- /dev/null +++ b/src/hai_agents/session_files/__init__.py @@ -0,0 +1,4 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + diff --git a/src/hai_agents/session_files/client.py b/src/hai_agents/session_files/client.py new file mode 100644 index 0000000..32230ab --- /dev/null +++ b/src/hai_agents/session_files/client.py @@ -0,0 +1,338 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.list_files_response import ListFilesResponse +from ..types.read_file_response import ReadFileResponse +from ..types.write_file_response import WriteFileResponse +from .raw_client import AsyncRawSessionFilesClient, RawSessionFilesClient + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class SessionFilesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawSessionFilesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawSessionFilesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawSessionFilesClient + """ + return self._raw_client + + def list_files( + self, + session_id: str, + *, + path: typing.Optional[str] = OMIT, + max_entries: typing.Optional[int] = OMIT, + modified_before: typing.Optional[dt.datetime] = OMIT, + name_after: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListFilesResponse: + """ + List files on the session's pod (allowlisted roots only), newest first, paged via the cursor. + + Parameters + ---------- + session_id : str + + path : typing.Optional[str] + + max_entries : typing.Optional[int] + + modified_before : typing.Optional[dt.datetime] + + name_after : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListFilesResponse + Successful Response + + Examples + -------- + from hai_agents import Client + + client = Client( + api_key="YOUR_API_KEY", + ) + client.session_files.list_files( + session_id="session_id", + ) + """ + _response = self._raw_client.list_files( + session_id, + path=path, + max_entries=max_entries, + modified_before=modified_before, + name_after=name_after, + request_options=request_options, + ) + return _response.data + + def read_file( + self, session_id: str, *, path: str, request_options: typing.Optional[RequestOptions] = None + ) -> ReadFileResponse: + """ + Read a file from the session's pod, returned base64-encoded (size-capped). + + Parameters + ---------- + session_id : str + + path : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ReadFileResponse + Successful Response + + Examples + -------- + from hai_agents import Client + + client = Client( + api_key="YOUR_API_KEY", + ) + client.session_files.read_file( + session_id="session_id", + path="path", + ) + """ + _response = self._raw_client.read_file(session_id, path=path, request_options=request_options) + return _response.data + + def write_file( + self, + session_id: str, + *, + path: str, + content_base64: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> WriteFileResponse: + """ + Write base64-encoded content to a file on the session's pod (size-capped). + + Parameters + ---------- + session_id : str + + path : str + + content_base64 : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WriteFileResponse + Successful Response + + Examples + -------- + from hai_agents import Client + + client = Client( + api_key="YOUR_API_KEY", + ) + client.session_files.write_file( + session_id="session_id", + path="path", + content_base64="content_base64", + ) + """ + _response = self._raw_client.write_file( + session_id, path=path, content_base64=content_base64, request_options=request_options + ) + return _response.data + + +class AsyncSessionFilesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawSessionFilesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawSessionFilesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawSessionFilesClient + """ + return self._raw_client + + async def list_files( + self, + session_id: str, + *, + path: typing.Optional[str] = OMIT, + max_entries: typing.Optional[int] = OMIT, + modified_before: typing.Optional[dt.datetime] = OMIT, + name_after: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListFilesResponse: + """ + List files on the session's pod (allowlisted roots only), newest first, paged via the cursor. + + Parameters + ---------- + session_id : str + + path : typing.Optional[str] + + max_entries : typing.Optional[int] + + modified_before : typing.Optional[dt.datetime] + + name_after : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListFilesResponse + Successful Response + + Examples + -------- + import asyncio + + from hai_agents import AsyncClient + + client = AsyncClient( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.session_files.list_files( + session_id="session_id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_files( + session_id, + path=path, + max_entries=max_entries, + modified_before=modified_before, + name_after=name_after, + request_options=request_options, + ) + return _response.data + + async def read_file( + self, session_id: str, *, path: str, request_options: typing.Optional[RequestOptions] = None + ) -> ReadFileResponse: + """ + Read a file from the session's pod, returned base64-encoded (size-capped). + + Parameters + ---------- + session_id : str + + path : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ReadFileResponse + Successful Response + + Examples + -------- + import asyncio + + from hai_agents import AsyncClient + + client = AsyncClient( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.session_files.read_file( + session_id="session_id", + path="path", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.read_file(session_id, path=path, request_options=request_options) + return _response.data + + async def write_file( + self, + session_id: str, + *, + path: str, + content_base64: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> WriteFileResponse: + """ + Write base64-encoded content to a file on the session's pod (size-capped). + + Parameters + ---------- + session_id : str + + path : str + + content_base64 : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WriteFileResponse + Successful Response + + Examples + -------- + import asyncio + + from hai_agents import AsyncClient + + client = AsyncClient( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.session_files.write_file( + session_id="session_id", + path="path", + content_base64="content_base64", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.write_file( + session_id, path=path, content_base64=content_base64, request_options=request_options + ) + return _response.data diff --git a/src/hai_agents/session_files/raw_client.py b/src/hai_agents/session_files/raw_client.py new file mode 100644 index 0000000..078aa1c --- /dev/null +++ b/src/hai_agents/session_files/raw_client.py @@ -0,0 +1,452 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import encode_path_param +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..errors.unprocessable_entity_error import UnprocessableEntityError +from ..types.http_validation_error import HttpValidationError +from ..types.list_files_response import ListFilesResponse +from ..types.read_file_response import ReadFileResponse +from ..types.write_file_response import WriteFileResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawSessionFilesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list_files( + self, + session_id: str, + *, + path: typing.Optional[str] = OMIT, + max_entries: typing.Optional[int] = OMIT, + modified_before: typing.Optional[dt.datetime] = OMIT, + name_after: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ListFilesResponse]: + """ + List files on the session's pod (allowlisted roots only), newest first, paged via the cursor. + + Parameters + ---------- + session_id : str + + path : typing.Optional[str] + + max_entries : typing.Optional[int] + + modified_before : typing.Optional[dt.datetime] + + name_after : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ListFilesResponse] + Successful Response + """ + _response = self._client_wrapper.httpx_client.request( + f"api/v2/sessions/{encode_path_param(session_id)}/files/list_files", + method="POST", + json={ + "path": path, + "max_entries": max_entries, + "modified_before": modified_before, + "name_after": name_after, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListFilesResponse, + parse_obj_as( + type_=ListFilesResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + HttpValidationError, + parse_obj_as( + type_=HttpValidationError, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def read_file( + self, session_id: str, *, path: str, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[ReadFileResponse]: + """ + Read a file from the session's pod, returned base64-encoded (size-capped). + + Parameters + ---------- + session_id : str + + path : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ReadFileResponse] + Successful Response + """ + _response = self._client_wrapper.httpx_client.request( + f"api/v2/sessions/{encode_path_param(session_id)}/files/read_file", + method="POST", + json={ + "path": path, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ReadFileResponse, + parse_obj_as( + type_=ReadFileResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + HttpValidationError, + parse_obj_as( + type_=HttpValidationError, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def write_file( + self, + session_id: str, + *, + path: str, + content_base64: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[WriteFileResponse]: + """ + Write base64-encoded content to a file on the session's pod (size-capped). + + Parameters + ---------- + session_id : str + + path : str + + content_base64 : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[WriteFileResponse] + Successful Response + """ + _response = self._client_wrapper.httpx_client.request( + f"api/v2/sessions/{encode_path_param(session_id)}/files/write_file", + method="POST", + json={ + "path": path, + "content_base64": content_base64, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WriteFileResponse, + parse_obj_as( + type_=WriteFileResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + HttpValidationError, + parse_obj_as( + type_=HttpValidationError, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawSessionFilesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list_files( + self, + session_id: str, + *, + path: typing.Optional[str] = OMIT, + max_entries: typing.Optional[int] = OMIT, + modified_before: typing.Optional[dt.datetime] = OMIT, + name_after: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ListFilesResponse]: + """ + List files on the session's pod (allowlisted roots only), newest first, paged via the cursor. + + Parameters + ---------- + session_id : str + + path : typing.Optional[str] + + max_entries : typing.Optional[int] + + modified_before : typing.Optional[dt.datetime] + + name_after : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ListFilesResponse] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/v2/sessions/{encode_path_param(session_id)}/files/list_files", + method="POST", + json={ + "path": path, + "max_entries": max_entries, + "modified_before": modified_before, + "name_after": name_after, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListFilesResponse, + parse_obj_as( + type_=ListFilesResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + HttpValidationError, + parse_obj_as( + type_=HttpValidationError, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def read_file( + self, session_id: str, *, path: str, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[ReadFileResponse]: + """ + Read a file from the session's pod, returned base64-encoded (size-capped). + + Parameters + ---------- + session_id : str + + path : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ReadFileResponse] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/v2/sessions/{encode_path_param(session_id)}/files/read_file", + method="POST", + json={ + "path": path, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ReadFileResponse, + parse_obj_as( + type_=ReadFileResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + HttpValidationError, + parse_obj_as( + type_=HttpValidationError, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def write_file( + self, + session_id: str, + *, + path: str, + content_base64: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[WriteFileResponse]: + """ + Write base64-encoded content to a file on the session's pod (size-capped). + + Parameters + ---------- + session_id : str + + path : str + + content_base64 : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[WriteFileResponse] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/v2/sessions/{encode_path_param(session_id)}/files/write_file", + method="POST", + json={ + "path": path, + "content_base64": content_base64, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WriteFileResponse, + parse_obj_as( + type_=WriteFileResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + HttpValidationError, + parse_obj_as( + type_=HttpValidationError, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/hai_agents/types/__init__.py b/src/hai_agents/types/__init__.py index 72a161b..8db75f2 100644 --- a/src/hai_agents/types/__init__.py +++ b/src/hai_agents/types/__init__.py @@ -55,12 +55,14 @@ from .error_event import ErrorEvent from .error_event_kind import ErrorEventKind from .feedback import Feedback + from .file_entry import FileEntry from .flow_event import FlowEvent from .http_validation_error import HttpValidationError from .image_content import ImageContent from .image_content_type import ImageContentType from .initiate_upload_response import InitiateUploadResponse from .json_value import JsonValue + from .list_files_response import ListFilesResponse from .live_view_url_data import LiveViewUrlData from .live_view_url_event import LiveViewUrlEvent from .managed_proxy_selection import ManagedProxySelection @@ -86,6 +88,7 @@ from .proxy_pool import ProxyPool from .quota_status import QuotaStatus from .quota_status_scope import QuotaStatusScope + from .read_file_response import ReadFileResponse from .request_start_data import RequestStartData from .request_start_dispatched_data import RequestStartDispatchedData from .request_start_dispatched_event import RequestStartDispatchedEvent @@ -146,6 +149,7 @@ from .webhook_record_last_delivery_status import WebhookRecordLastDeliveryStatus from .webhook_with_secret import WebhookWithSecret from .webhook_with_secret_last_delivery_status import WebhookWithSecretLastDeliveryStatus + from .write_file_response import WriteFileResponse _dynamic_imports: typing.Dict[str, str] = { "ActiveStateChangeData": ".active_state_change_data", "ActiveStateChangeDataState": ".active_state_change_data_state", @@ -198,12 +202,14 @@ "ErrorEvent": ".error_event", "ErrorEventKind": ".error_event_kind", "Feedback": ".feedback", + "FileEntry": ".file_entry", "FlowEvent": ".flow_event", "HttpValidationError": ".http_validation_error", "ImageContent": ".image_content", "ImageContentType": ".image_content_type", "InitiateUploadResponse": ".initiate_upload_response", "JsonValue": ".json_value", + "ListFilesResponse": ".list_files_response", "LiveViewUrlData": ".live_view_url_data", "LiveViewUrlEvent": ".live_view_url_event", "ManagedProxySelection": ".managed_proxy_selection", @@ -229,6 +235,7 @@ "ProxyPool": ".proxy_pool", "QuotaStatus": ".quota_status", "QuotaStatusScope": ".quota_status_scope", + "ReadFileResponse": ".read_file_response", "RequestStartData": ".request_start_data", "RequestStartDispatchedData": ".request_start_dispatched_data", "RequestStartDispatchedEvent": ".request_start_dispatched_event", @@ -285,6 +292,7 @@ "WebhookRecordLastDeliveryStatus": ".webhook_record_last_delivery_status", "WebhookWithSecret": ".webhook_with_secret", "WebhookWithSecretLastDeliveryStatus": ".webhook_with_secret_last_delivery_status", + "WriteFileResponse": ".write_file_response", } @@ -361,12 +369,14 @@ def __dir__(): "ErrorEvent", "ErrorEventKind", "Feedback", + "FileEntry", "FlowEvent", "HttpValidationError", "ImageContent", "ImageContentType", "InitiateUploadResponse", "JsonValue", + "ListFilesResponse", "LiveViewUrlData", "LiveViewUrlEvent", "ManagedProxySelection", @@ -392,6 +402,7 @@ def __dir__(): "ProxyPool", "QuotaStatus", "QuotaStatusScope", + "ReadFileResponse", "RequestStartData", "RequestStartDispatchedData", "RequestStartDispatchedEvent", @@ -448,4 +459,5 @@ def __dir__(): "WebhookRecordLastDeliveryStatus", "WebhookWithSecret", "WebhookWithSecretLastDeliveryStatus", + "WriteFileResponse", ] diff --git a/src/hai_agents/types/file_entry.py b/src/hai_agents/types/file_entry.py new file mode 100644 index 0000000..92cd220 --- /dev/null +++ b/src/hai_agents/types/file_entry.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class FileEntry(UniversalBaseModel): + """ + One entry in a directory listing. + """ + + name: str + path: str + size_bytes: int + modified_at: dt.datetime + is_dir: bool + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/hai_agents/types/list_files_response.py b/src/hai_agents/types/list_files_response.py new file mode 100644 index 0000000..811379b --- /dev/null +++ b/src/hai_agents/types/list_files_response.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .file_entry import FileEntry + + +class ListFilesResponse(UniversalBaseModel): + """ + Directory listing, newest first; ``truncated`` when capped at ``max_entries``. + """ + + path: str + files: typing.List[FileEntry] + truncated: bool + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/hai_agents/types/read_file_response.py b/src/hai_agents/types/read_file_response.py new file mode 100644 index 0000000..946b1d4 --- /dev/null +++ b/src/hai_agents/types/read_file_response.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ReadFileResponse(UniversalBaseModel): + """ + A file's base64 contents and metadata. + """ + + path: str + content_base64: str + size_bytes: int + modified_at: dt.datetime + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/hai_agents/types/session_summary.py b/src/hai_agents/types/session_summary.py index 2c05e39..7a2e635 100644 --- a/src/hai_agents/types/session_summary.py +++ b/src/hai_agents/types/session_summary.py @@ -26,6 +26,11 @@ class SessionSummary(UniversalBaseModel): created_at: dt.datetime started_at: typing.Optional[dt.datetime] = None finished_at: typing.Optional[dt.datetime] = None + last_activity_at: typing.Optional[dt.datetime] = pydantic.Field(default=None) + """ + When the session's latest event was ingested (agent steps, status changes, user messages). Null for sessions that predate this field. + """ + anonymized_at: typing.Optional[dt.datetime] = pydantic.Field(default=None) """ When the session's PII text and screenshots were irreversibly anonymized. Null if not anonymized. diff --git a/src/hai_agents/types/write_file_response.py b/src/hai_agents/types/write_file_response.py new file mode 100644 index 0000000..446e9ec --- /dev/null +++ b/src/hai_agents/types/write_file_response.py @@ -0,0 +1,24 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class WriteFileResponse(UniversalBaseModel): + """ + The written file's resolved path and byte count. + """ + + path: str + size_bytes: int + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/uv.lock b/uv.lock index e0ac9d0..950d035 100644 --- a/uv.lock +++ b/uv.lock @@ -144,7 +144,7 @@ wheels = [ [[package]] name = "hai-agents" -version = "1.0.7" +version = "1.0.8" source = { editable = "." } dependencies = [ { name = "httpx" },