diff --git a/openapi.json b/openapi.json index 80db169..8a143ef 100644 --- a/openapi.json +++ b/openapi.json @@ -2096,13 +2096,34 @@ "Agents" ], "summary": "Create Agent", - "description": "Create an agent..", + "description": "Create an agent at revision 1.", "operationId": "create_agent_api_v2_agents_post", "security": [ { "HTTPBearer": [] } ], + "parameters": [ + { + "name": "message", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "maxLength": 500 + }, + { + "type": "null" + } + ], + "description": "Optional note stored on the revision this write creates, such as why the change was made.", + "title": "Message" + }, + "description": "Optional note stored on the revision this write creates, such as why the change was made." + } + ], "requestBody": { "required": true, "content": { @@ -2122,6 +2143,14 @@ "$ref": "#/components/schemas/Agent" } } + }, + "headers": { + "ETag": { + "description": "The revision number of the returned spec, e.g. ``\"3\"``.", + "schema": { + "type": "string" + } + } } }, "422": { @@ -2203,6 +2232,25 @@ }, "description": "``true``: reserved ``h/`` agents only. ``false``: org agents only." }, + { + "name": "include", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "const": "revision", + "type": "string" + }, + { + "type": "null" + } + ], + "description": "``revision``: each item is an ``AgentWithRevision``, the spec plus the agent's current ``revision`` number. Omitted, items are plain ``Agent`` specs.", + "title": "Include" + }, + "description": "``revision``: each item is an ``AgentWithRevision``, the spec plus the agent's current ``revision`` number. Omitted, items are plain ``Agent`` specs." + }, { "name": "page", "in": "query", @@ -2282,6 +2330,211 @@ } } } + }, + "x-include-item-schemas": { + "revision": { + "$ref": "#/components/schemas/AgentWithRevision" + } + } + } + }, + "/api/v2/agents/{agent_name}/revisions": { + "get": { + "tags": [ + "Agents" + ], + "summary": "List Agent Revisions", + "description": "List an agent's revisions without their specs, newest first. Read one with ``?revision=N``.\n\nUnauthenticated callers can list reserved ``h/`` agents only.", + "operationId": "list_agent_revisions_api_v2_agents__agent_name__revisions_get", + "security": [ + { + "HTTPBearer": [] + } + ], + "parameters": [ + { + "name": "agent_name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Agent Name" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "description": "Page number (1-based)", + "default": 1, + "title": "Page" + }, + "description": "Page number (1-based)" + }, + { + "name": "size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 1000, + "minimum": 1, + "description": "Number of items per page", + "default": 10, + "title": "Size" + }, + "description": "Number of items per page" + }, + { + "name": "sort", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "array", + "items": { + "enum": [ + "number", + "-number" + ], + "type": "string" + } + }, + { + "type": "null" + } + ], + "description": "Sort by field", + "default": [ + "-number" + ], + "title": "Sort" + }, + "description": "Sort by field" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Page_AgentRevisionRecord_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v2/agents/{agent_name}/revisions/{number}/restore": { + "post": { + "tags": [ + "Agents" + ], + "summary": "Restore Agent Revision", + "description": "Make revision ``number``'s spec current again, as a new revision with the message ``Restored from vN``.\n\nValidated and authorised like ``PUT``. The new revision's ``restored_from`` is ``number``.\nRestoring a spec that is already current changes nothing and returns the current revision.", + "operationId": "restore_agent_revision_api_v2_agents__agent_name__revisions__number__restore_post", + "security": [ + { + "HTTPBearer": [] + } + ], + "parameters": [ + { + "name": "agent_name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Agent Name" + } + }, + { + "name": "number", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "minimum": 1, + "description": "The revision whose spec becomes current again.", + "title": "Number" + }, + "description": "The revision whose spec becomes current again." + }, + { + "name": "If-Match", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The ``ETag`` of the revision this write is based on, e.g. ``\"3\"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally.", + "title": "If-Match" + }, + "description": "The ``ETag`` of the revision this write is based on, e.g. ``\"3\"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally." + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Agent" + } + } + }, + "headers": { + "ETag": { + "description": "The revision number of the returned spec, e.g. ``\"3\"``.", + "schema": { + "type": "string" + } + } + } + }, + "412": { + "description": "``If-Match`` names a revision that is no longer current; ``ETag`` has the current one.", + "headers": { + "ETag": { + "description": "The current revision number, e.g. ``\"4\"``.", + "schema": { + "type": "string" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } } } }, @@ -2291,7 +2544,7 @@ "Agents" ], "summary": "Get Agent", - "description": "Fetch by identifier; 404 if not visible. ``resolve=true`` materialises spec leaves.", + "description": "Fetch by name at its latest revision, or at ``revision``; 404 if not visible or no such revision.\n\nThe stored spec carries its revision as ``ETag``. A resolved spec does not, since it also\nreflects the current environments, skills, and subagents it names. ``resolve=true``\nmaterialises spec leaves.", "operationId": "get_agent_api_v2_agents__agent_name__get", "security": [ { @@ -2308,6 +2561,25 @@ "title": "Agent Name" } }, + { + "name": "revision", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer", + "minimum": 1 + }, + { + "type": "null" + } + ], + "description": "Read this revision instead of the latest. References to an agent elsewhere, such as a session's ``agent``, pin a revision as ``name:N`` instead.", + "title": "Revision" + }, + "description": "Read this revision instead of the latest. References to an agent elsewhere, such as a session's ``agent``, pin a revision as ``name:N`` instead." + }, { "name": "resolve", "in": "query", @@ -2330,6 +2602,14 @@ "$ref": "#/components/schemas/Agent" } } + }, + "headers": { + "ETag": { + "description": "The revision number of the returned spec, e.g. ``\"3\"``. Absent with ``resolve=true``, whose body also reflects the current environments, skills, and subagents it names.", + "schema": { + "type": "string" + } + } } }, "422": { @@ -2349,7 +2629,7 @@ "Agents" ], "summary": "Update Agent", - "description": "Replace ``spec``. ``spec.name`` must match the URL identifier; renames are not supported.", + "description": "Replace ``spec`` as a new revision. ``spec.name`` must match the URL identifier; renames are not supported.\n\nSending the spec that is already current changes nothing and returns the current revision.", "operationId": "update_agent_api_v2_agents__agent_name__put", "security": [ { @@ -2365,6 +2645,43 @@ "type": "string", "title": "Agent Name" } + }, + { + "name": "message", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "maxLength": 500 + }, + { + "type": "null" + } + ], + "description": "Optional note stored on the revision this write creates, such as why the change was made.", + "title": "Message" + }, + "description": "Optional note stored on the revision this write creates, such as why the change was made." + }, + { + "name": "If-Match", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The ``ETag`` of the revision this write is based on, e.g. ``\"3\"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally.", + "title": "If-Match" + }, + "description": "The ``ETag`` of the revision this write is based on, e.g. ``\"3\"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally." } ], "requestBody": { @@ -2386,6 +2703,25 @@ "$ref": "#/components/schemas/Agent" } } + }, + "headers": { + "ETag": { + "description": "The revision number of the returned spec, e.g. ``\"3\"``.", + "schema": { + "type": "string" + } + } + } + }, + "412": { + "description": "``If-Match`` names a revision that is no longer current; ``ETag`` has the current one.", + "headers": { + "ETag": { + "description": "The current revision number, e.g. ``\"4\"``.", + "schema": { + "type": "string" + } + } } }, "422": { @@ -2405,7 +2741,7 @@ "Agents" ], "summary": "Patch Agent", - "description": "Partially update an agent spec; only provided fields change.", + "description": "Partially update an agent spec as a new revision; only provided fields change.", "operationId": "patch_agent_api_v2_agents__agent_name__patch", "security": [ { @@ -2421,6 +2757,43 @@ "type": "string", "title": "Agent Name" } + }, + { + "name": "message", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "maxLength": 500 + }, + { + "type": "null" + } + ], + "description": "Optional note stored on the revision this write creates, such as why the change was made.", + "title": "Message" + }, + "description": "Optional note stored on the revision this write creates, such as why the change was made." + }, + { + "name": "If-Match", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The ``ETag`` of the revision this write is based on, e.g. ``\"3\"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally.", + "title": "If-Match" + }, + "description": "The ``ETag`` of the revision this write is based on, e.g. ``\"3\"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally." } ], "requestBody": { @@ -2442,6 +2815,25 @@ "$ref": "#/components/schemas/Agent" } } + }, + "headers": { + "ETag": { + "description": "The revision number of the returned spec, e.g. ``\"3\"``.", + "schema": { + "type": "string" + } + } + } + }, + "412": { + "description": "``If-Match`` names a revision that is no longer current; ``ETag`` has the current one.", + "headers": { + "ETag": { + "description": "The current revision number, e.g. ``\"4\"``.", + "schema": { + "type": "string" + } + } } }, "422": { @@ -2461,7 +2853,7 @@ "Agents" ], "summary": "Delete Agent", - "description": "Delete by identifier. Reserved rows: H employee only.", + "description": "Delete by identifier, with its revisions. Reserved rows: H employee only.", "operationId": "delete_agent_api_v2_agents__agent_name__delete", "security": [ { @@ -2477,12 +2869,41 @@ "type": "string", "title": "Agent Name" } + }, + { + "name": "If-Match", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The ``ETag`` of the revision this write is based on, e.g. ``\"3\"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally.", + "title": "If-Match" + }, + "description": "The ``ETag`` of the revision this write is based on, e.g. ``\"3\"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally." } ], "responses": { "204": { "description": "Successful Response" }, + "412": { + "description": "``If-Match`` names a revision that is no longer current; ``ETag`` has the current one.", + "headers": { + "ETag": { + "description": "The current revision number, e.g. ``\"4\"``.", + "schema": { + "type": "string" + } + } + } + }, "422": { "description": "Validation Error", "content": { @@ -4513,7 +4934,7 @@ }, "environments": { "items": { - "anyOf": [ + "oneOf": [ { "type": "string" }, @@ -4572,7 +4993,7 @@ "anyOf": [ { "items": { - "anyOf": [ + "oneOf": [ { "type": "string" }, @@ -4594,7 +5015,7 @@ "anyOf": [ { "items": { - "anyOf": [ + "oneOf": [ { "type": "string" }, @@ -4813,6 +5234,67 @@ "title": "AgentEvent", "description": "Event emitted by the agent loop itself; ``data`` is discriminated by ``kind``." }, + "AgentRevisionRecord": { + "properties": { + "number": { + "type": "integer", + "minimum": 1.0, + "title": "Number", + "description": "Revision number, from 1. Read its spec with ``GET /v2/agents/{agent_name}?revision={number}``." + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "created_by": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "Created By", + "description": "User who made the change. Null when unknown, and on reserved ``h/`` agents for non-H callers." + }, + "message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Message", + "description": "Note sent with the change, if any." + }, + "restored_from": { + "anyOf": [ + { + "type": "integer", + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "Restored From", + "description": "The earlier revision whose spec this one restored, through ``POST .../revisions/{N}/restore``." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "number", + "created_at" + ], + "title": "AgentRevisionRecord", + "description": "One stored revision of an agent, without its spec." + }, "AgentRunStatusChangeData": { "properties": { "status": { @@ -6542,6 +7024,32 @@ "title": "OnePasswordConfig", "description": "1Password vault provider config." }, + "Page_AgentRevisionRecord_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/AgentRevisionRecord" + }, + "type": "array", + "title": "Items" + }, + "total": { + "type": "integer", + "title": "Total" + }, + "page": { + "type": "integer", + "title": "Page" + } + }, + "type": "object", + "required": [ + "items", + "total", + "page" + ], + "title": "Page[AgentRevisionRecord]" + }, "Page_Agent_": { "properties": { "items": { @@ -6727,36 +7235,22 @@ "PatchAgent": { "properties": { "description": { - "anyOf": [ - { - "type": "string", - "minLength": 1 - }, - { - "type": "null" - } - ], + "type": "string", + "minLength": 1, "title": "Description" }, - "environments": { - "anyOf": [ - { - "items": { - "anyOf": [ - { - "type": "string" - }, - { - "$ref": "#/components/schemas/Environment" - } - ] + "environments": { + "items": { + "oneOf": [ + { + "type": "string" }, - "type": "array" - }, - { - "type": "null" - } - ], + { + "$ref": "#/components/schemas/Environment" + } + ] + }, + "type": "array", "title": "Environments" }, "model": { @@ -6802,7 +7296,7 @@ "anyOf": [ { "items": { - "anyOf": [ + "oneOf": [ { "type": "string" }, @@ -6823,7 +7317,7 @@ "anyOf": [ { "items": { - "anyOf": [ + "oneOf": [ { "type": "string" }, @@ -6874,40 +7368,19 @@ "PatchEnvironment": { "properties": { "host": { - "anyOf": [ - { - "type": "string", - "enum": [ - "user_device", - "cloud" - ] - }, - { - "type": "null" - } + "type": "string", + "enum": [ + "user_device", + "cloud" ], "title": "Host" }, "start_url": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], + "type": "string", "title": "Start Url" }, "headless": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], + "type": "boolean", "title": "Headless" }, "session_id": { @@ -6922,52 +7395,31 @@ "title": "Session Id" }, "mode": { - "anyOf": [ + "oneOf": [ { - "oneOf": [ - { - "$ref": "#/components/schemas/BrowserVisualMode" - }, - { - "$ref": "#/components/schemas/BrowserTextMode" - } - ], - "discriminator": { - "propertyName": "type", - "mapping": { - "text": "#/components/schemas/BrowserTextMode", - "visual": "#/components/schemas/BrowserVisualMode" - } - } + "$ref": "#/components/schemas/BrowserVisualMode" }, { - "type": "null" + "$ref": "#/components/schemas/BrowserTextMode" } ], - "title": "Mode" + "title": "Mode", + "discriminator": { + "propertyName": "type", + "mapping": { + "text": "#/components/schemas/BrowserTextMode", + "visual": "#/components/schemas/BrowserVisualMode" + } + } }, "lazy": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], + "type": "boolean", "title": "Lazy" }, "settle_s": { - "anyOf": [ - { - "type": "number", - "maximum": 10.0, - "minimum": 0.0 - }, - { - "type": "null" - } - ], + "type": "number", + "maximum": 10.0, + "minimum": 0.0, "title": "Settle S" }, "vault_id": { @@ -6995,25 +7447,11 @@ "title": "Browser Profile Id" }, "use_default_browser_profile": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], + "type": "boolean", "title": "Use Default Browser Profile" }, "persist_browser_profile": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], + "type": "boolean", "title": "Persist Browser Profile" }, "network": { @@ -7058,27 +7496,13 @@ "PatchSkill": { "properties": { "description": { - "anyOf": [ - { - "type": "string", - "minLength": 1 - }, - { - "type": "null" - } - ], + "type": "string", + "minLength": 1, "title": "Description" }, "body": { - "anyOf": [ - { - "type": "string", - "minLength": 1 - }, - { - "type": "null" - } - ], + "type": "string", + "minLength": 1, "title": "Body" }, "source": { @@ -7117,33 +7541,19 @@ "title": "Compatibility" }, "files": { - "anyOf": [ - { - "additionalProperties": { - "type": "string", - "contentMediaType": "application/octet-stream" - }, - "type": "object", - "maxProperties": 511 - }, - { - "type": "null" - } - ], + "additionalProperties": { + "type": "string", + "contentMediaType": "application/octet-stream" + }, + "type": "object", + "maxProperties": 511, "title": "Files" }, "executable_files": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], + "items": { + "type": "string" + }, + "type": "array", "title": "Executable Files" } }, @@ -7614,11 +8024,24 @@ "title": "Id" }, "request": { - "$ref": "#/components/schemas/SessionRequest" + "$ref": "#/components/schemas/SessionRequest", + "description": "The request the session runs. Secrets read back as ``***``, with keys and list lengths kept: MCP server ``headers``, ``env`` and ``args``, code sandbox ``env``, and browser ``proxy_url``." }, "status": { "$ref": "#/components/schemas/SessionStatus" }, + "agent_revision": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Agent Revision", + "description": "Revision of the catalog agent the session runs; ``request.agent`` is that revision, resolved, with ``request.overrides`` applied. Null for inline agent specs and sessions created before agent revisions." + }, "agent_view_url": { "anyOf": [ { @@ -7854,7 +8277,7 @@ "SessionRequest": { "properties": { "agent": { - "anyOf": [ + "oneOf": [ { "type": "string" }, @@ -7866,7 +8289,7 @@ "description": "Agent to run: a registered agent's name, or an inline Agent definition." }, "messages": { - "anyOf": [ + "oneOf": [ { "type": "string" }, @@ -8086,6 +8509,18 @@ ], "title": "Agent" }, + "agent_revision": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Agent Revision", + "description": "Revision of the catalog agent the session runs; null for inline specs and older sessions." + }, "status": { "$ref": "#/components/schemas/TrajectoryStatus" }, @@ -8526,16 +8961,9 @@ "UpdateSchedule": { "properties": { "name": { - "anyOf": [ - { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - { - "type": "null" - } - ], + "type": "string", + "maxLength": 255, + "minLength": 1, "title": "Name" }, "description": { @@ -8551,24 +8979,10 @@ "title": "Description" }, "timing": { - "anyOf": [ - { - "$ref": "#/components/schemas/CronTiming" - }, - { - "type": "null" - } - ] + "$ref": "#/components/schemas/CronTiming" }, "session_request": { - "anyOf": [ - { - "$ref": "#/components/schemas/SessionRequest" - }, - { - "type": "null" - } - ] + "$ref": "#/components/schemas/SessionRequest" }, "repeats": { "anyOf": [ @@ -8590,30 +9004,16 @@ "UpdateWebhook": { "properties": { "url": { - "anyOf": [ - { - "type": "string", - "maxLength": 2048, - "minLength": 1 - }, - { - "type": "null" - } - ], + "type": "string", + "maxLength": 2048, + "minLength": 1, "title": "Url" }, "enabled_events": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], + "items": { + "type": "string" + }, + "type": "array", "title": "Enabled Events" }, "description": { @@ -8629,14 +9029,7 @@ "title": "Description" }, "disabled": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], + "type": "boolean", "title": "Disabled" } }, @@ -9279,6 +9672,27 @@ ], "title": "WriteFileResponse", "description": "The written file's resolved path and byte count." + }, + "AgentWithRevision": { + "title": "AgentWithRevision", + "description": "An agent spec with its current revision number, as listed by ``GET /v2/agents?include=revision``.\n\nOnly listed, never accepted as a body: ``revision`` is not part of the spec.", + "allOf": [ + { + "$ref": "#/components/schemas/Agent" + } + ], + "type": "object", + "properties": { + "revision": { + "description": "Current revision number of the agent, as its ``ETag`` carries it.", + "minimum": 1, + "title": "Revision", + "type": "integer" + } + }, + "required": [ + "revision" + ] } }, "securitySchemes": { diff --git a/src/hai_agents/__init__.py b/src/hai_agents/__init__.py index 86cfeec..c1c0e88 100644 --- a/src/hai_agents/__init__.py +++ b/src/hai_agents/__init__.py @@ -27,6 +27,7 @@ AgentEventData_PolicyEvent, AgentEventData_ToolResult, AgentReasoningEffort, + AgentRevisionRecord, AgentRunStatusChangeData, AgentRunStatusChangeDataStatus, AgentRunStatusChangeEvent, @@ -34,6 +35,7 @@ AgentStartedData, AgentStartedEvent, AgentSubagentsItem, + AgentWithRevision, Android, AnswerEvent, AnswerEventAnswer, @@ -92,6 +94,7 @@ OnePasswordConfig, OnePasswordConfigProvider, PageAgent, + PageAgentRevisionRecord, PageScheduleRecord, PageScheduleRunRecord, PageSessionEvent, @@ -169,7 +172,7 @@ WorkstationHost, WriteFileResponse, ) - from .errors import NotFoundError, UnprocessableEntityError + from .errors import NotFoundError, PreconditionFailedError, UnprocessableEntityError from . import ( agents, browser_profiles, @@ -184,6 +187,8 @@ ) from ._default_clients import DefaultAioHttpClient, DefaultAsyncHttpxClient from .agents import ( + ListAgentRevisionsRequestSortItem, + ListAgentsRequestInclude, ListAgentsRequestSortItem, PatchAgentEnvironmentsItem, PatchAgentReasoningEffort, @@ -263,6 +268,7 @@ "AgentEventData_PolicyEvent": ".types", "AgentEventData_ToolResult": ".types", "AgentReasoningEffort": ".types", + "AgentRevisionRecord": ".types", "AgentRunStatusChangeData": ".types", "AgentRunStatusChangeDataStatus": ".types", "AgentRunStatusChangeEvent": ".types", @@ -270,6 +276,7 @@ "AgentStartedData": ".types", "AgentStartedEvent": ".types", "AgentSubagentsItem": ".types", + "AgentWithRevision": ".types", "Android": ".types", "AnswerEvent": ".types", "AnswerEventAnswer": ".types", @@ -325,6 +332,8 @@ "ImageContentType": ".types", "InitiateUploadResponse": ".types", "JsonValue": ".types", + "ListAgentRevisionsRequestSortItem": ".agents", + "ListAgentsRequestInclude": ".agents", "ListAgentsRequestSortItem": ".agents", "ListEnvironmentsRequestSortItem": ".environments", "ListFilesResponse": ".types", @@ -351,6 +360,7 @@ "OnePasswordConfig": ".types", "OnePasswordConfigProvider": ".types", "PageAgent": ".types", + "PageAgentRevisionRecord": ".types", "PageScheduleRecord": ".types", "PageScheduleRunRecord": ".types", "PageSessionEvent": ".types", @@ -367,6 +377,7 @@ "PatchEnvironmentMode_Visual": ".environments", "PauseSchedule": ".types", "PolicyEvent": ".types", + "PreconditionFailedError": ".errors", "ProxyPool": ".types", "QuotaStatus": ".types", "QuotaStatusScope": ".types", @@ -521,6 +532,7 @@ def __dir__(): "AgentEventData_PolicyEvent", "AgentEventData_ToolResult", "AgentReasoningEffort", + "AgentRevisionRecord", "AgentRunStatusChangeData", "AgentRunStatusChangeDataStatus", "AgentRunStatusChangeEvent", @@ -528,6 +540,7 @@ def __dir__(): "AgentStartedData", "AgentStartedEvent", "AgentSubagentsItem", + "AgentWithRevision", "Android", "AnswerEvent", "AnswerEventAnswer", @@ -583,6 +596,8 @@ def __dir__(): "ImageContentType", "InitiateUploadResponse", "JsonValue", + "ListAgentRevisionsRequestSortItem", + "ListAgentsRequestInclude", "ListAgentsRequestSortItem", "ListEnvironmentsRequestSortItem", "ListFilesResponse", @@ -609,6 +624,7 @@ def __dir__(): "OnePasswordConfig", "OnePasswordConfigProvider", "PageAgent", + "PageAgentRevisionRecord", "PageScheduleRecord", "PageScheduleRunRecord", "PageSessionEvent", @@ -625,6 +641,7 @@ def __dir__(): "PatchEnvironmentMode_Visual", "PauseSchedule", "PolicyEvent", + "PreconditionFailedError", "ProxyPool", "QuotaStatus", "QuotaStatusScope", diff --git a/src/hai_agents/agents/__init__.py b/src/hai_agents/agents/__init__.py index c5da2c3..8d161c7 100644 --- a/src/hai_agents/agents/__init__.py +++ b/src/hai_agents/agents/__init__.py @@ -7,6 +7,8 @@ if typing.TYPE_CHECKING: from .types import ( + ListAgentRevisionsRequestSortItem, + ListAgentsRequestInclude, ListAgentsRequestSortItem, PatchAgentEnvironmentsItem, PatchAgentReasoningEffort, @@ -14,6 +16,8 @@ PatchAgentSubagentsItem, ) _dynamic_imports: typing.Dict[str, str] = { + "ListAgentRevisionsRequestSortItem": ".types", + "ListAgentsRequestInclude": ".types", "ListAgentsRequestSortItem": ".types", "PatchAgentEnvironmentsItem": ".types", "PatchAgentReasoningEffort": ".types", @@ -44,6 +48,8 @@ def __dir__(): __all__ = [ + "ListAgentRevisionsRequestSortItem", + "ListAgentsRequestInclude", "ListAgentsRequestSortItem", "PatchAgentEnvironmentsItem", "PatchAgentReasoningEffort", diff --git a/src/hai_agents/agents/client.py b/src/hai_agents/agents/client.py index faecdff..d372524 100644 --- a/src/hai_agents/agents/client.py +++ b/src/hai_agents/agents/client.py @@ -10,8 +10,11 @@ from ..types.agent_skills_item import AgentSkillsItem from ..types.agent_subagents_item import AgentSubagentsItem from ..types.page_agent import PageAgent +from ..types.page_agent_revision_record import PageAgentRevisionRecord from ..types.tool_definition import ToolDefinition from .raw_client import AsyncRawAgentsClient, RawAgentsClient +from .types.list_agent_revisions_request_sort_item import ListAgentRevisionsRequestSortItem +from .types.list_agents_request_include import ListAgentsRequestInclude from .types.list_agents_request_sort_item import ListAgentsRequestSortItem from .types.patch_agent_environments_item import PatchAgentEnvironmentsItem from .types.patch_agent_reasoning_effort import PatchAgentReasoningEffort @@ -43,6 +46,7 @@ def list_agents( agent_name: typing.Optional[str] = None, search: typing.Optional[str] = None, reserved: typing.Optional[bool] = None, + include: typing.Optional[ListAgentsRequestInclude] = None, page: typing.Optional[int] = None, size: typing.Optional[int] = None, sort: typing.Optional[typing.Sequence[ListAgentsRequestSortItem]] = None, @@ -62,6 +66,9 @@ def list_agents( reserved : typing.Optional[bool] ``true``: reserved ``h/`` agents only. ``false``: org agents only. + include : typing.Optional[ListAgentsRequestInclude] + ``revision``: each item is an ``AgentWithRevision``, the spec plus the agent's current ``revision`` number. Omitted, items are plain ``Agent`` specs. + page : typing.Optional[int] Page number (1-based) @@ -92,6 +99,7 @@ def list_agents( agent_name=agent_name, search=search, reserved=reserved, + include=include, page=page, size=size, sort=sort, @@ -105,6 +113,7 @@ def create_agent( name: str, description: str, environments: typing.Sequence[AgentEnvironmentsItem], + message: typing.Optional[str] = None, model: typing.Optional[str] = OMIT, reasoning_effort: typing.Optional[AgentReasoningEffort] = OMIT, instructions: typing.Optional[str] = OMIT, @@ -115,7 +124,7 @@ def create_agent( request_options: typing.Optional[RequestOptions] = None, ) -> Agent: """ - Create an agent.. + Create an agent at revision 1. Parameters ---------- @@ -128,6 +137,9 @@ def create_agent( environments : typing.Sequence[AgentEnvironmentsItem] Environments the agent runs in. Each entry is a registered environment's id or an inline definition. At most one per kind. Required unless the agent delegates to subagents or uses client-executed custom tools. + message : typing.Optional[str] + Optional note stored on the revision this write creates, such as why the change was made. + model : typing.Optional[str] Model that serves the agent. Defaults to the platform model if omitted. @@ -174,6 +186,7 @@ def create_agent( name=name, description=description, environments=environments, + message=message, model=model, reasoning_effort=reasoning_effort, instructions=instructions, @@ -185,20 +198,128 @@ def create_agent( ) return _response.data + def list_agent_revisions( + self, + agent_name: str, + *, + page: typing.Optional[int] = None, + size: typing.Optional[int] = None, + sort: typing.Optional[typing.Sequence[ListAgentRevisionsRequestSortItem]] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PageAgentRevisionRecord: + """ + List an agent's revisions without their specs, newest first. Read one with ``?revision=N``. + + Unauthenticated callers can list reserved ``h/`` agents only. + + Parameters + ---------- + agent_name : str + + page : typing.Optional[int] + Page number (1-based) + + size : typing.Optional[int] + Number of items per page + + sort : typing.Optional[typing.Sequence[ListAgentRevisionsRequestSortItem]] + Sort by field + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PageAgentRevisionRecord + Successful Response + + Examples + -------- + from hai_agents import Client + + client = Client( + api_key="YOUR_API_KEY", + ) + client.agents.list_agent_revisions( + agent_name="agent_name", + ) + """ + _response = self._raw_client.list_agent_revisions( + agent_name, page=page, size=size, sort=sort, request_options=request_options + ) + return _response.data + + def restore_agent_revision( + self, + agent_name: str, + number: int, + *, + if_match: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> Agent: + """ + Make revision ``number``'s spec current again, as a new revision with the message ``Restored from vN``. + + Validated and authorised like ``PUT``. The new revision's ``restored_from`` is ``number``. + Restoring a spec that is already current changes nothing and returns the current revision. + + Parameters + ---------- + agent_name : str + + number : int + The revision whose spec becomes current again. + + if_match : typing.Optional[str] + The ``ETag`` of the revision this write is based on, e.g. ``"3"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Agent + Successful Response + + Examples + -------- + from hai_agents import Client + + client = Client( + api_key="YOUR_API_KEY", + ) + client.agents.restore_agent_revision( + agent_name="agent_name", + number=1, + ) + """ + _response = self._raw_client.restore_agent_revision( + agent_name, number, if_match=if_match, request_options=request_options + ) + return _response.data + def get_agent( self, agent_name: str, *, + revision: typing.Optional[int] = None, resolve: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None, ) -> Agent: """ - Fetch by identifier; 404 if not visible. ``resolve=true`` materialises spec leaves. + Fetch by name at its latest revision, or at ``revision``; 404 if not visible or no such revision. + + The stored spec carries its revision as ``ETag``. A resolved spec does not, since it also + reflects the current environments, skills, and subagents it names. ``resolve=true`` + materialises spec leaves. Parameters ---------- agent_name : str + revision : typing.Optional[int] + Read this revision instead of the latest. References to an agent elsewhere, such as a session's ``agent``, pin a revision as ``name:N`` instead. + resolve : typing.Optional[bool] Materialise string environment/skill/subagent leaves into full specs. @@ -221,7 +342,9 @@ def get_agent( agent_name="agent_name", ) """ - _response = self._raw_client.get_agent(agent_name, resolve=resolve, request_options=request_options) + _response = self._raw_client.get_agent( + agent_name, revision=revision, resolve=resolve, request_options=request_options + ) return _response.data def update_agent( @@ -231,6 +354,8 @@ def update_agent( name: str, description: str, environments: typing.Sequence[AgentEnvironmentsItem], + message: typing.Optional[str] = None, + if_match: typing.Optional[str] = None, model: typing.Optional[str] = OMIT, reasoning_effort: typing.Optional[AgentReasoningEffort] = OMIT, instructions: typing.Optional[str] = OMIT, @@ -241,7 +366,9 @@ def update_agent( request_options: typing.Optional[RequestOptions] = None, ) -> Agent: """ - Replace ``spec``. ``spec.name`` must match the URL identifier; renames are not supported. + Replace ``spec`` as a new revision. ``spec.name`` must match the URL identifier; renames are not supported. + + Sending the spec that is already current changes nothing and returns the current revision. Parameters ---------- @@ -256,6 +383,12 @@ def update_agent( environments : typing.Sequence[AgentEnvironmentsItem] Environments the agent runs in. Each entry is a registered environment's id or an inline definition. At most one per kind. Required unless the agent delegates to subagents or uses client-executed custom tools. + message : typing.Optional[str] + Optional note stored on the revision this write creates, such as why the change was made. + + if_match : typing.Optional[str] + The ``ETag`` of the revision this write is based on, e.g. ``"3"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally. + model : typing.Optional[str] Model that serves the agent. Defaults to the platform model if omitted. @@ -304,6 +437,8 @@ def update_agent( name=name, description=description, environments=environments, + message=message, + if_match=if_match, model=model, reasoning_effort=reasoning_effort, instructions=instructions, @@ -315,14 +450,23 @@ def update_agent( ) return _response.data - def delete_agent(self, agent_name: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: + def delete_agent( + self, + agent_name: str, + *, + if_match: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> None: """ - Delete by identifier. Reserved rows: H employee only. + Delete by identifier, with its revisions. Reserved rows: H employee only. Parameters ---------- agent_name : str + if_match : typing.Optional[str] + The ``ETag`` of the revision this write is based on, e.g. ``"3"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -341,13 +485,15 @@ def delete_agent(self, agent_name: str, *, request_options: typing.Optional[Requ agent_name="agent_name", ) """ - _response = self._raw_client.delete_agent(agent_name, request_options=request_options) + _response = self._raw_client.delete_agent(agent_name, if_match=if_match, request_options=request_options) return _response.data def patch_agent( self, agent_name: str, *, + message: typing.Optional[str] = None, + if_match: typing.Optional[str] = None, description: typing.Optional[str] = OMIT, environments: typing.Optional[typing.Sequence[PatchAgentEnvironmentsItem]] = OMIT, model: typing.Optional[str] = OMIT, @@ -360,12 +506,18 @@ def patch_agent( request_options: typing.Optional[RequestOptions] = None, ) -> Agent: """ - Partially update an agent spec; only provided fields change. + Partially update an agent spec as a new revision; only provided fields change. Parameters ---------- agent_name : str + message : typing.Optional[str] + Optional note stored on the revision this write creates, such as why the change was made. + + if_match : typing.Optional[str] + The ``ETag`` of the revision this write is based on, e.g. ``"3"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally. + description : typing.Optional[str] environments : typing.Optional[typing.Sequence[PatchAgentEnvironmentsItem]] @@ -405,6 +557,8 @@ def patch_agent( """ _response = self._raw_client.patch_agent( agent_name, + message=message, + if_match=if_match, description=description, environments=environments, model=model, @@ -440,6 +594,7 @@ async def list_agents( agent_name: typing.Optional[str] = None, search: typing.Optional[str] = None, reserved: typing.Optional[bool] = None, + include: typing.Optional[ListAgentsRequestInclude] = None, page: typing.Optional[int] = None, size: typing.Optional[int] = None, sort: typing.Optional[typing.Sequence[ListAgentsRequestSortItem]] = None, @@ -459,6 +614,9 @@ async def list_agents( reserved : typing.Optional[bool] ``true``: reserved ``h/`` agents only. ``false``: org agents only. + include : typing.Optional[ListAgentsRequestInclude] + ``revision``: each item is an ``AgentWithRevision``, the spec plus the agent's current ``revision`` number. Omitted, items are plain ``Agent`` specs. + page : typing.Optional[int] Page number (1-based) @@ -497,6 +655,7 @@ async def main() -> None: agent_name=agent_name, search=search, reserved=reserved, + include=include, page=page, size=size, sort=sort, @@ -510,6 +669,7 @@ async def create_agent( name: str, description: str, environments: typing.Sequence[AgentEnvironmentsItem], + message: typing.Optional[str] = None, model: typing.Optional[str] = OMIT, reasoning_effort: typing.Optional[AgentReasoningEffort] = OMIT, instructions: typing.Optional[str] = OMIT, @@ -520,7 +680,7 @@ async def create_agent( request_options: typing.Optional[RequestOptions] = None, ) -> Agent: """ - Create an agent.. + Create an agent at revision 1. Parameters ---------- @@ -533,6 +693,9 @@ async def create_agent( environments : typing.Sequence[AgentEnvironmentsItem] Environments the agent runs in. Each entry is a registered environment's id or an inline definition. At most one per kind. Required unless the agent delegates to subagents or uses client-executed custom tools. + message : typing.Optional[str] + Optional note stored on the revision this write creates, such as why the change was made. + model : typing.Optional[str] Model that serves the agent. Defaults to the platform model if omitted. @@ -587,6 +750,7 @@ async def main() -> None: name=name, description=description, environments=environments, + message=message, model=model, reasoning_effort=reasoning_effort, instructions=instructions, @@ -598,20 +762,144 @@ async def main() -> None: ) return _response.data + async def list_agent_revisions( + self, + agent_name: str, + *, + page: typing.Optional[int] = None, + size: typing.Optional[int] = None, + sort: typing.Optional[typing.Sequence[ListAgentRevisionsRequestSortItem]] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> PageAgentRevisionRecord: + """ + List an agent's revisions without their specs, newest first. Read one with ``?revision=N``. + + Unauthenticated callers can list reserved ``h/`` agents only. + + Parameters + ---------- + agent_name : str + + page : typing.Optional[int] + Page number (1-based) + + size : typing.Optional[int] + Number of items per page + + sort : typing.Optional[typing.Sequence[ListAgentRevisionsRequestSortItem]] + Sort by field + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PageAgentRevisionRecord + Successful Response + + Examples + -------- + import asyncio + + from hai_agents import AsyncClient + + client = AsyncClient( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.agents.list_agent_revisions( + agent_name="agent_name", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_agent_revisions( + agent_name, page=page, size=size, sort=sort, request_options=request_options + ) + return _response.data + + async def restore_agent_revision( + self, + agent_name: str, + number: int, + *, + if_match: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> Agent: + """ + Make revision ``number``'s spec current again, as a new revision with the message ``Restored from vN``. + + Validated and authorised like ``PUT``. The new revision's ``restored_from`` is ``number``. + Restoring a spec that is already current changes nothing and returns the current revision. + + Parameters + ---------- + agent_name : str + + number : int + The revision whose spec becomes current again. + + if_match : typing.Optional[str] + The ``ETag`` of the revision this write is based on, e.g. ``"3"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Agent + Successful Response + + Examples + -------- + import asyncio + + from hai_agents import AsyncClient + + client = AsyncClient( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.agents.restore_agent_revision( + agent_name="agent_name", + number=1, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.restore_agent_revision( + agent_name, number, if_match=if_match, request_options=request_options + ) + return _response.data + async def get_agent( self, agent_name: str, *, + revision: typing.Optional[int] = None, resolve: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None, ) -> Agent: """ - Fetch by identifier; 404 if not visible. ``resolve=true`` materialises spec leaves. + Fetch by name at its latest revision, or at ``revision``; 404 if not visible or no such revision. + + The stored spec carries its revision as ``ETag``. A resolved spec does not, since it also + reflects the current environments, skills, and subagents it names. ``resolve=true`` + materialises spec leaves. Parameters ---------- agent_name : str + revision : typing.Optional[int] + Read this revision instead of the latest. References to an agent elsewhere, such as a session's ``agent``, pin a revision as ``name:N`` instead. + resolve : typing.Optional[bool] Materialise string environment/skill/subagent leaves into full specs. @@ -642,7 +930,9 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._raw_client.get_agent(agent_name, resolve=resolve, request_options=request_options) + _response = await self._raw_client.get_agent( + agent_name, revision=revision, resolve=resolve, request_options=request_options + ) return _response.data async def update_agent( @@ -652,6 +942,8 @@ async def update_agent( name: str, description: str, environments: typing.Sequence[AgentEnvironmentsItem], + message: typing.Optional[str] = None, + if_match: typing.Optional[str] = None, model: typing.Optional[str] = OMIT, reasoning_effort: typing.Optional[AgentReasoningEffort] = OMIT, instructions: typing.Optional[str] = OMIT, @@ -662,7 +954,9 @@ async def update_agent( request_options: typing.Optional[RequestOptions] = None, ) -> Agent: """ - Replace ``spec``. ``spec.name`` must match the URL identifier; renames are not supported. + Replace ``spec`` as a new revision. ``spec.name`` must match the URL identifier; renames are not supported. + + Sending the spec that is already current changes nothing and returns the current revision. Parameters ---------- @@ -677,6 +971,12 @@ async def update_agent( environments : typing.Sequence[AgentEnvironmentsItem] Environments the agent runs in. Each entry is a registered environment's id or an inline definition. At most one per kind. Required unless the agent delegates to subagents or uses client-executed custom tools. + message : typing.Optional[str] + Optional note stored on the revision this write creates, such as why the change was made. + + if_match : typing.Optional[str] + The ``ETag`` of the revision this write is based on, e.g. ``"3"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally. + model : typing.Optional[str] Model that serves the agent. Defaults to the platform model if omitted. @@ -733,6 +1033,8 @@ async def main() -> None: name=name, description=description, environments=environments, + message=message, + if_match=if_match, model=model, reasoning_effort=reasoning_effort, instructions=instructions, @@ -744,14 +1046,23 @@ async def main() -> None: ) return _response.data - async def delete_agent(self, agent_name: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: + async def delete_agent( + self, + agent_name: str, + *, + if_match: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> None: """ - Delete by identifier. Reserved rows: H employee only. + Delete by identifier, with its revisions. Reserved rows: H employee only. Parameters ---------- agent_name : str + if_match : typing.Optional[str] + The ``ETag`` of the revision this write is based on, e.g. ``"3"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -778,13 +1089,15 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._raw_client.delete_agent(agent_name, request_options=request_options) + _response = await self._raw_client.delete_agent(agent_name, if_match=if_match, request_options=request_options) return _response.data async def patch_agent( self, agent_name: str, *, + message: typing.Optional[str] = None, + if_match: typing.Optional[str] = None, description: typing.Optional[str] = OMIT, environments: typing.Optional[typing.Sequence[PatchAgentEnvironmentsItem]] = OMIT, model: typing.Optional[str] = OMIT, @@ -797,12 +1110,18 @@ async def patch_agent( request_options: typing.Optional[RequestOptions] = None, ) -> Agent: """ - Partially update an agent spec; only provided fields change. + Partially update an agent spec as a new revision; only provided fields change. Parameters ---------- agent_name : str + message : typing.Optional[str] + Optional note stored on the revision this write creates, such as why the change was made. + + if_match : typing.Optional[str] + The ``ETag`` of the revision this write is based on, e.g. ``"3"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally. + description : typing.Optional[str] environments : typing.Optional[typing.Sequence[PatchAgentEnvironmentsItem]] @@ -850,6 +1169,8 @@ async def main() -> None: """ _response = await self._raw_client.patch_agent( agent_name, + message=message, + if_match=if_match, description=description, environments=environments, model=model, diff --git a/src/hai_agents/agents/raw_client.py b/src/hai_agents/agents/raw_client.py index 9ca2afa..16819e1 100644 --- a/src/hai_agents/agents/raw_client.py +++ b/src/hai_agents/agents/raw_client.py @@ -11,6 +11,7 @@ from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..core.serialization import convert_and_respect_annotation_metadata +from ..errors.precondition_failed_error import PreconditionFailedError from ..errors.unprocessable_entity_error import UnprocessableEntityError from ..types.agent import Agent from ..types.agent_environments_item import AgentEnvironmentsItem @@ -19,7 +20,10 @@ from ..types.agent_subagents_item import AgentSubagentsItem from ..types.http_validation_error import HttpValidationError from ..types.page_agent import PageAgent +from ..types.page_agent_revision_record import PageAgentRevisionRecord from ..types.tool_definition import ToolDefinition +from .types.list_agent_revisions_request_sort_item import ListAgentRevisionsRequestSortItem +from .types.list_agents_request_include import ListAgentsRequestInclude from .types.list_agents_request_sort_item import ListAgentsRequestSortItem from .types.patch_agent_environments_item import PatchAgentEnvironmentsItem from .types.patch_agent_reasoning_effort import PatchAgentReasoningEffort @@ -41,6 +45,7 @@ def list_agents( agent_name: typing.Optional[str] = None, search: typing.Optional[str] = None, reserved: typing.Optional[bool] = None, + include: typing.Optional[ListAgentsRequestInclude] = None, page: typing.Optional[int] = None, size: typing.Optional[int] = None, sort: typing.Optional[typing.Sequence[ListAgentsRequestSortItem]] = None, @@ -60,6 +65,9 @@ def list_agents( reserved : typing.Optional[bool] ``true``: reserved ``h/`` agents only. ``false``: org agents only. + include : typing.Optional[ListAgentsRequestInclude] + ``revision``: each item is an ``AgentWithRevision``, the spec plus the agent's current ``revision`` number. Omitted, items are plain ``Agent`` specs. + page : typing.Optional[int] Page number (1-based) @@ -84,6 +92,7 @@ def list_agents( "agent_name": agent_name, "search": search, "reserved": reserved, + "include": include, "page": page, "size": size, "sort": sort, @@ -126,6 +135,7 @@ def create_agent( name: str, description: str, environments: typing.Sequence[AgentEnvironmentsItem], + message: typing.Optional[str] = None, model: typing.Optional[str] = OMIT, reasoning_effort: typing.Optional[AgentReasoningEffort] = OMIT, instructions: typing.Optional[str] = OMIT, @@ -136,7 +146,7 @@ def create_agent( request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[Agent]: """ - Create an agent.. + Create an agent at revision 1. Parameters ---------- @@ -149,6 +159,9 @@ def create_agent( environments : typing.Sequence[AgentEnvironmentsItem] Environments the agent runs in. Each entry is a registered environment's id or an inline definition. At most one per kind. Required unless the agent delegates to subagents or uses client-executed custom tools. + message : typing.Optional[str] + Optional note stored on the revision this write creates, such as why the change was made. + model : typing.Optional[str] Model that serves the agent. Defaults to the platform model if omitted. @@ -181,6 +194,9 @@ def create_agent( _response = self._client_wrapper.httpx_client.request( "api/v2/agents", method="POST", + params={ + "message": message, + }, json={ "name": name, "description": description, @@ -239,20 +255,184 @@ def create_agent( ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + def list_agent_revisions( + self, + agent_name: str, + *, + page: typing.Optional[int] = None, + size: typing.Optional[int] = None, + sort: typing.Optional[typing.Sequence[ListAgentRevisionsRequestSortItem]] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[PageAgentRevisionRecord]: + """ + List an agent's revisions without their specs, newest first. Read one with ``?revision=N``. + + Unauthenticated callers can list reserved ``h/`` agents only. + + Parameters + ---------- + agent_name : str + + page : typing.Optional[int] + Page number (1-based) + + size : typing.Optional[int] + Number of items per page + + sort : typing.Optional[typing.Sequence[ListAgentRevisionsRequestSortItem]] + Sort by field + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[PageAgentRevisionRecord] + Successful Response + """ + _response = self._client_wrapper.httpx_client.request( + f"api/v2/agents/{encode_path_param(agent_name)}/revisions", + method="GET", + params={ + "page": page, + "size": size, + "sort": sort, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PageAgentRevisionRecord, + parse_obj_as( + type_=PageAgentRevisionRecord, # 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 restore_agent_revision( + self, + agent_name: str, + number: int, + *, + if_match: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[Agent]: + """ + Make revision ``number``'s spec current again, as a new revision with the message ``Restored from vN``. + + Validated and authorised like ``PUT``. The new revision's ``restored_from`` is ``number``. + Restoring a spec that is already current changes nothing and returns the current revision. + + Parameters + ---------- + agent_name : str + + number : int + The revision whose spec becomes current again. + + if_match : typing.Optional[str] + The ``ETag`` of the revision this write is based on, e.g. ``"3"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Agent] + Successful Response + """ + _response = self._client_wrapper.httpx_client.request( + f"api/v2/agents/{encode_path_param(agent_name)}/revisions/{encode_path_param(number)}/restore", + method="POST", + headers={ + "If-Match": str(if_match) if if_match is not None else None, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Agent, + parse_obj_as( + type_=Agent, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 412: + raise PreconditionFailedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + 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 get_agent( self, agent_name: str, *, + revision: typing.Optional[int] = None, resolve: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[Agent]: """ - Fetch by identifier; 404 if not visible. ``resolve=true`` materialises spec leaves. + Fetch by name at its latest revision, or at ``revision``; 404 if not visible or no such revision. + + The stored spec carries its revision as ``ETag``. A resolved spec does not, since it also + reflects the current environments, skills, and subagents it names. ``resolve=true`` + materialises spec leaves. Parameters ---------- agent_name : str + revision : typing.Optional[int] + Read this revision instead of the latest. References to an agent elsewhere, such as a session's ``agent``, pin a revision as ``name:N`` instead. + resolve : typing.Optional[bool] Materialise string environment/skill/subagent leaves into full specs. @@ -268,6 +448,7 @@ def get_agent( f"api/v2/agents/{encode_path_param(agent_name)}", method="GET", params={ + "revision": revision, "resolve": resolve, }, request_options=request_options, @@ -309,6 +490,8 @@ def update_agent( name: str, description: str, environments: typing.Sequence[AgentEnvironmentsItem], + message: typing.Optional[str] = None, + if_match: typing.Optional[str] = None, model: typing.Optional[str] = OMIT, reasoning_effort: typing.Optional[AgentReasoningEffort] = OMIT, instructions: typing.Optional[str] = OMIT, @@ -319,7 +502,9 @@ def update_agent( request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[Agent]: """ - Replace ``spec``. ``spec.name`` must match the URL identifier; renames are not supported. + Replace ``spec`` as a new revision. ``spec.name`` must match the URL identifier; renames are not supported. + + Sending the spec that is already current changes nothing and returns the current revision. Parameters ---------- @@ -334,6 +519,12 @@ def update_agent( environments : typing.Sequence[AgentEnvironmentsItem] Environments the agent runs in. Each entry is a registered environment's id or an inline definition. At most one per kind. Required unless the agent delegates to subagents or uses client-executed custom tools. + message : typing.Optional[str] + Optional note stored on the revision this write creates, such as why the change was made. + + if_match : typing.Optional[str] + The ``ETag`` of the revision this write is based on, e.g. ``"3"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally. + model : typing.Optional[str] Model that serves the agent. Defaults to the platform model if omitted. @@ -366,6 +557,9 @@ def update_agent( _response = self._client_wrapper.httpx_client.request( f"api/v2/agents/{encode_path_param(agent_name)}", method="PUT", + params={ + "message": message, + }, json={ "name": name, "description": description, @@ -390,6 +584,7 @@ def update_agent( }, headers={ "content-type": "application/json", + "If-Match": str(if_match) if if_match is not None else None, }, request_options=request_options, omit=OMIT, @@ -404,6 +599,17 @@ def update_agent( ), ) return HttpResponse(response=_response, data=_data) + if _response.status_code == 412: + raise PreconditionFailedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 422: raise UnprocessableEntityError( headers=dict(_response.headers), @@ -425,15 +631,22 @@ def update_agent( raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) def delete_agent( - self, agent_name: str, *, request_options: typing.Optional[RequestOptions] = None + self, + agent_name: str, + *, + if_match: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[None]: """ - Delete by identifier. Reserved rows: H employee only. + Delete by identifier, with its revisions. Reserved rows: H employee only. Parameters ---------- agent_name : str + if_match : typing.Optional[str] + The ``ETag`` of the revision this write is based on, e.g. ``"3"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -444,11 +657,25 @@ def delete_agent( _response = self._client_wrapper.httpx_client.request( f"api/v2/agents/{encode_path_param(agent_name)}", method="DELETE", + headers={ + "If-Match": str(if_match) if if_match is not None else None, + }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: return HttpResponse(response=_response, data=None) + if _response.status_code == 412: + raise PreconditionFailedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 422: raise UnprocessableEntityError( headers=dict(_response.headers), @@ -473,6 +700,8 @@ def patch_agent( self, agent_name: str, *, + message: typing.Optional[str] = None, + if_match: typing.Optional[str] = None, description: typing.Optional[str] = OMIT, environments: typing.Optional[typing.Sequence[PatchAgentEnvironmentsItem]] = OMIT, model: typing.Optional[str] = OMIT, @@ -485,12 +714,18 @@ def patch_agent( request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[Agent]: """ - Partially update an agent spec; only provided fields change. + Partially update an agent spec as a new revision; only provided fields change. Parameters ---------- agent_name : str + message : typing.Optional[str] + Optional note stored on the revision this write creates, such as why the change was made. + + if_match : typing.Optional[str] + The ``ETag`` of the revision this write is based on, e.g. ``"3"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally. + description : typing.Optional[str] environments : typing.Optional[typing.Sequence[PatchAgentEnvironmentsItem]] @@ -520,12 +755,13 @@ def patch_agent( _response = self._client_wrapper.httpx_client.request( f"api/v2/agents/{encode_path_param(agent_name)}", method="PATCH", + params={ + "message": message, + }, json={ "description": description, "environments": convert_and_respect_annotation_metadata( - object_=environments, - annotation=typing.Optional[typing.Sequence[PatchAgentEnvironmentsItem]], - direction="write", + object_=environments, annotation=typing.Sequence[PatchAgentEnvironmentsItem], direction="write" ), "model": model, "reasoning_effort": reasoning_effort, @@ -545,6 +781,7 @@ def patch_agent( }, headers={ "content-type": "application/json", + "If-Match": str(if_match) if if_match is not None else None, }, request_options=request_options, omit=OMIT, @@ -559,6 +796,17 @@ def patch_agent( ), ) return HttpResponse(response=_response, data=_data) + if _response.status_code == 412: + raise PreconditionFailedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 422: raise UnprocessableEntityError( headers=dict(_response.headers), @@ -590,6 +838,7 @@ async def list_agents( agent_name: typing.Optional[str] = None, search: typing.Optional[str] = None, reserved: typing.Optional[bool] = None, + include: typing.Optional[ListAgentsRequestInclude] = None, page: typing.Optional[int] = None, size: typing.Optional[int] = None, sort: typing.Optional[typing.Sequence[ListAgentsRequestSortItem]] = None, @@ -609,6 +858,9 @@ async def list_agents( reserved : typing.Optional[bool] ``true``: reserved ``h/`` agents only. ``false``: org agents only. + include : typing.Optional[ListAgentsRequestInclude] + ``revision``: each item is an ``AgentWithRevision``, the spec plus the agent's current ``revision`` number. Omitted, items are plain ``Agent`` specs. + page : typing.Optional[int] Page number (1-based) @@ -633,6 +885,7 @@ async def list_agents( "agent_name": agent_name, "search": search, "reserved": reserved, + "include": include, "page": page, "size": size, "sort": sort, @@ -675,6 +928,7 @@ async def create_agent( name: str, description: str, environments: typing.Sequence[AgentEnvironmentsItem], + message: typing.Optional[str] = None, model: typing.Optional[str] = OMIT, reasoning_effort: typing.Optional[AgentReasoningEffort] = OMIT, instructions: typing.Optional[str] = OMIT, @@ -685,7 +939,7 @@ async def create_agent( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[Agent]: """ - Create an agent.. + Create an agent at revision 1. Parameters ---------- @@ -698,6 +952,9 @@ async def create_agent( environments : typing.Sequence[AgentEnvironmentsItem] Environments the agent runs in. Each entry is a registered environment's id or an inline definition. At most one per kind. Required unless the agent delegates to subagents or uses client-executed custom tools. + message : typing.Optional[str] + Optional note stored on the revision this write creates, such as why the change was made. + model : typing.Optional[str] Model that serves the agent. Defaults to the platform model if omitted. @@ -730,6 +987,9 @@ async def create_agent( _response = await self._client_wrapper.httpx_client.request( "api/v2/agents", method="POST", + params={ + "message": message, + }, json={ "name": name, "description": description, @@ -788,20 +1048,184 @@ async def create_agent( ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + async def list_agent_revisions( + self, + agent_name: str, + *, + page: typing.Optional[int] = None, + size: typing.Optional[int] = None, + sort: typing.Optional[typing.Sequence[ListAgentRevisionsRequestSortItem]] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[PageAgentRevisionRecord]: + """ + List an agent's revisions without their specs, newest first. Read one with ``?revision=N``. + + Unauthenticated callers can list reserved ``h/`` agents only. + + Parameters + ---------- + agent_name : str + + page : typing.Optional[int] + Page number (1-based) + + size : typing.Optional[int] + Number of items per page + + sort : typing.Optional[typing.Sequence[ListAgentRevisionsRequestSortItem]] + Sort by field + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[PageAgentRevisionRecord] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/v2/agents/{encode_path_param(agent_name)}/revisions", + method="GET", + params={ + "page": page, + "size": size, + "sort": sort, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PageAgentRevisionRecord, + parse_obj_as( + type_=PageAgentRevisionRecord, # 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 restore_agent_revision( + self, + agent_name: str, + number: int, + *, + if_match: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[Agent]: + """ + Make revision ``number``'s spec current again, as a new revision with the message ``Restored from vN``. + + Validated and authorised like ``PUT``. The new revision's ``restored_from`` is ``number``. + Restoring a spec that is already current changes nothing and returns the current revision. + + Parameters + ---------- + agent_name : str + + number : int + The revision whose spec becomes current again. + + if_match : typing.Optional[str] + The ``ETag`` of the revision this write is based on, e.g. ``"3"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Agent] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/v2/agents/{encode_path_param(agent_name)}/revisions/{encode_path_param(number)}/restore", + method="POST", + headers={ + "If-Match": str(if_match) if if_match is not None else None, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Agent, + parse_obj_as( + type_=Agent, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 412: + raise PreconditionFailedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + 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 get_agent( self, agent_name: str, *, + revision: typing.Optional[int] = None, resolve: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[Agent]: """ - Fetch by identifier; 404 if not visible. ``resolve=true`` materialises spec leaves. + Fetch by name at its latest revision, or at ``revision``; 404 if not visible or no such revision. + + The stored spec carries its revision as ``ETag``. A resolved spec does not, since it also + reflects the current environments, skills, and subagents it names. ``resolve=true`` + materialises spec leaves. Parameters ---------- agent_name : str + revision : typing.Optional[int] + Read this revision instead of the latest. References to an agent elsewhere, such as a session's ``agent``, pin a revision as ``name:N`` instead. + resolve : typing.Optional[bool] Materialise string environment/skill/subagent leaves into full specs. @@ -817,6 +1241,7 @@ async def get_agent( f"api/v2/agents/{encode_path_param(agent_name)}", method="GET", params={ + "revision": revision, "resolve": resolve, }, request_options=request_options, @@ -858,6 +1283,8 @@ async def update_agent( name: str, description: str, environments: typing.Sequence[AgentEnvironmentsItem], + message: typing.Optional[str] = None, + if_match: typing.Optional[str] = None, model: typing.Optional[str] = OMIT, reasoning_effort: typing.Optional[AgentReasoningEffort] = OMIT, instructions: typing.Optional[str] = OMIT, @@ -868,7 +1295,9 @@ async def update_agent( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[Agent]: """ - Replace ``spec``. ``spec.name`` must match the URL identifier; renames are not supported. + Replace ``spec`` as a new revision. ``spec.name`` must match the URL identifier; renames are not supported. + + Sending the spec that is already current changes nothing and returns the current revision. Parameters ---------- @@ -883,6 +1312,12 @@ async def update_agent( environments : typing.Sequence[AgentEnvironmentsItem] Environments the agent runs in. Each entry is a registered environment's id or an inline definition. At most one per kind. Required unless the agent delegates to subagents or uses client-executed custom tools. + message : typing.Optional[str] + Optional note stored on the revision this write creates, such as why the change was made. + + if_match : typing.Optional[str] + The ``ETag`` of the revision this write is based on, e.g. ``"3"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally. + model : typing.Optional[str] Model that serves the agent. Defaults to the platform model if omitted. @@ -915,6 +1350,9 @@ async def update_agent( _response = await self._client_wrapper.httpx_client.request( f"api/v2/agents/{encode_path_param(agent_name)}", method="PUT", + params={ + "message": message, + }, json={ "name": name, "description": description, @@ -939,6 +1377,7 @@ async def update_agent( }, headers={ "content-type": "application/json", + "If-Match": str(if_match) if if_match is not None else None, }, request_options=request_options, omit=OMIT, @@ -953,6 +1392,17 @@ async def update_agent( ), ) return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 412: + raise PreconditionFailedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 422: raise UnprocessableEntityError( headers=dict(_response.headers), @@ -974,15 +1424,22 @@ async def update_agent( raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) async def delete_agent( - self, agent_name: str, *, request_options: typing.Optional[RequestOptions] = None + self, + agent_name: str, + *, + if_match: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[None]: """ - Delete by identifier. Reserved rows: H employee only. + Delete by identifier, with its revisions. Reserved rows: H employee only. Parameters ---------- agent_name : str + if_match : typing.Optional[str] + The ``ETag`` of the revision this write is based on, e.g. ``"3"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -993,11 +1450,25 @@ async def delete_agent( _response = await self._client_wrapper.httpx_client.request( f"api/v2/agents/{encode_path_param(agent_name)}", method="DELETE", + headers={ + "If-Match": str(if_match) if if_match is not None else None, + }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: return AsyncHttpResponse(response=_response, data=None) + if _response.status_code == 412: + raise PreconditionFailedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 422: raise UnprocessableEntityError( headers=dict(_response.headers), @@ -1022,6 +1493,8 @@ async def patch_agent( self, agent_name: str, *, + message: typing.Optional[str] = None, + if_match: typing.Optional[str] = None, description: typing.Optional[str] = OMIT, environments: typing.Optional[typing.Sequence[PatchAgentEnvironmentsItem]] = OMIT, model: typing.Optional[str] = OMIT, @@ -1034,12 +1507,18 @@ async def patch_agent( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[Agent]: """ - Partially update an agent spec; only provided fields change. + Partially update an agent spec as a new revision; only provided fields change. Parameters ---------- agent_name : str + message : typing.Optional[str] + Optional note stored on the revision this write creates, such as why the change was made. + + if_match : typing.Optional[str] + The ``ETag`` of the revision this write is based on, e.g. ``"3"``. If the resource has moved past it the write is refused with 412. Omit it, or send ``*``, to write unconditionally. + description : typing.Optional[str] environments : typing.Optional[typing.Sequence[PatchAgentEnvironmentsItem]] @@ -1069,12 +1548,13 @@ async def patch_agent( _response = await self._client_wrapper.httpx_client.request( f"api/v2/agents/{encode_path_param(agent_name)}", method="PATCH", + params={ + "message": message, + }, json={ "description": description, "environments": convert_and_respect_annotation_metadata( - object_=environments, - annotation=typing.Optional[typing.Sequence[PatchAgentEnvironmentsItem]], - direction="write", + object_=environments, annotation=typing.Sequence[PatchAgentEnvironmentsItem], direction="write" ), "model": model, "reasoning_effort": reasoning_effort, @@ -1094,6 +1574,7 @@ async def patch_agent( }, headers={ "content-type": "application/json", + "If-Match": str(if_match) if if_match is not None else None, }, request_options=request_options, omit=OMIT, @@ -1108,6 +1589,17 @@ async def patch_agent( ), ) return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 412: + raise PreconditionFailedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 422: raise UnprocessableEntityError( headers=dict(_response.headers), diff --git a/src/hai_agents/agents/types/__init__.py b/src/hai_agents/agents/types/__init__.py index ede5020..0e58034 100644 --- a/src/hai_agents/agents/types/__init__.py +++ b/src/hai_agents/agents/types/__init__.py @@ -6,12 +6,16 @@ from importlib import import_module if typing.TYPE_CHECKING: + from .list_agent_revisions_request_sort_item import ListAgentRevisionsRequestSortItem + from .list_agents_request_include import ListAgentsRequestInclude from .list_agents_request_sort_item import ListAgentsRequestSortItem from .patch_agent_environments_item import PatchAgentEnvironmentsItem from .patch_agent_reasoning_effort import PatchAgentReasoningEffort from .patch_agent_skills_item import PatchAgentSkillsItem from .patch_agent_subagents_item import PatchAgentSubagentsItem _dynamic_imports: typing.Dict[str, str] = { + "ListAgentRevisionsRequestSortItem": ".list_agent_revisions_request_sort_item", + "ListAgentsRequestInclude": ".list_agents_request_include", "ListAgentsRequestSortItem": ".list_agents_request_sort_item", "PatchAgentEnvironmentsItem": ".patch_agent_environments_item", "PatchAgentReasoningEffort": ".patch_agent_reasoning_effort", @@ -42,6 +46,8 @@ def __dir__(): __all__ = [ + "ListAgentRevisionsRequestSortItem", + "ListAgentsRequestInclude", "ListAgentsRequestSortItem", "PatchAgentEnvironmentsItem", "PatchAgentReasoningEffort", diff --git a/src/hai_agents/agents/types/list_agent_revisions_request_sort_item.py b/src/hai_agents/agents/types/list_agent_revisions_request_sort_item.py new file mode 100644 index 0000000..5f1d527 --- /dev/null +++ b/src/hai_agents/agents/types/list_agent_revisions_request_sort_item.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListAgentRevisionsRequestSortItem = typing.Union[typing.Literal["number", "-number"], typing.Any] diff --git a/src/hai_agents/agents/types/list_agents_request_include.py b/src/hai_agents/agents/types/list_agents_request_include.py new file mode 100644 index 0000000..f9d6dc4 --- /dev/null +++ b/src/hai_agents/agents/types/list_agents_request_include.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListAgentsRequestInclude = typing.Union[typing.Literal["revision"], typing.Any] diff --git a/src/hai_agents/environments/raw_client.py b/src/hai_agents/environments/raw_client.py index 77f8d0e..6661454 100644 --- a/src/hai_agents/environments/raw_client.py +++ b/src/hai_agents/environments/raw_client.py @@ -409,7 +409,7 @@ def patch_environment( "headless": headless, "session_id": session_id, "mode": convert_and_respect_annotation_metadata( - object_=mode, annotation=typing.Optional[PatchEnvironmentMode], direction="write" + object_=mode, annotation=PatchEnvironmentMode, direction="write" ), "lazy": lazy, "settle_s": settle_s, @@ -841,7 +841,7 @@ async def patch_environment( "headless": headless, "session_id": session_id, "mode": convert_and_respect_annotation_metadata( - object_=mode, annotation=typing.Optional[PatchEnvironmentMode], direction="write" + object_=mode, annotation=PatchEnvironmentMode, direction="write" ), "lazy": lazy, "settle_s": settle_s, diff --git a/src/hai_agents/errors/__init__.py b/src/hai_agents/errors/__init__.py index ff6cb74..f35b4b8 100644 --- a/src/hai_agents/errors/__init__.py +++ b/src/hai_agents/errors/__init__.py @@ -7,9 +7,11 @@ if typing.TYPE_CHECKING: from .not_found_error import NotFoundError + from .precondition_failed_error import PreconditionFailedError from .unprocessable_entity_error import UnprocessableEntityError _dynamic_imports: typing.Dict[str, str] = { "NotFoundError": ".not_found_error", + "PreconditionFailedError": ".precondition_failed_error", "UnprocessableEntityError": ".unprocessable_entity_error", } @@ -35,4 +37,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["NotFoundError", "UnprocessableEntityError"] +__all__ = ["NotFoundError", "PreconditionFailedError", "UnprocessableEntityError"] diff --git a/src/hai_agents/errors/precondition_failed_error.py b/src/hai_agents/errors/precondition_failed_error.py new file mode 100644 index 0000000..1a576b2 --- /dev/null +++ b/src/hai_agents/errors/precondition_failed_error.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.api_error import ApiError + + +class PreconditionFailedError(ApiError): + def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=412, headers=headers, body=body) diff --git a/src/hai_agents/schedules/raw_client.py b/src/hai_agents/schedules/raw_client.py index 9e8c8da..74e7046 100644 --- a/src/hai_agents/schedules/raw_client.py +++ b/src/hai_agents/schedules/raw_client.py @@ -328,10 +328,10 @@ def update_schedule( "name": name, "description": description, "timing": convert_and_respect_annotation_metadata( - object_=timing, annotation=typing.Optional[CronTiming], direction="write" + object_=timing, annotation=CronTiming, direction="write" ), "session_request": convert_and_respect_annotation_metadata( - object_=session_request, annotation=typing.Optional[SessionRequest], direction="write" + object_=session_request, annotation=SessionRequest, direction="write" ), "repeats": repeats, }, @@ -917,10 +917,10 @@ async def update_schedule( "name": name, "description": description, "timing": convert_and_respect_annotation_metadata( - object_=timing, annotation=typing.Optional[CronTiming], direction="write" + object_=timing, annotation=CronTiming, direction="write" ), "session_request": convert_and_respect_annotation_metadata( - object_=session_request, annotation=typing.Optional[SessionRequest], direction="write" + object_=session_request, annotation=SessionRequest, direction="write" ), "repeats": repeats, }, diff --git a/src/hai_agents/skills/client.py b/src/hai_agents/skills/client.py index 3d606bb..ea0b790 100644 --- a/src/hai_agents/skills/client.py +++ b/src/hai_agents/skills/client.py @@ -299,7 +299,7 @@ def patch_skill( source: typing.Optional[str] = OMIT, url_pattern: typing.Optional[str] = OMIT, compatibility: typing.Optional[str] = OMIT, - files: typing.Optional[typing.Dict[str, typing.Optional[str]]] = OMIT, + files: typing.Optional[typing.Dict[str, str]] = OMIT, executable_files: typing.Optional[typing.Sequence[str]] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> Skill: @@ -320,7 +320,7 @@ def patch_skill( compatibility : typing.Optional[str] - files : typing.Optional[typing.Dict[str, typing.Optional[str]]] + files : typing.Optional[typing.Dict[str, str]] executable_files : typing.Optional[typing.Sequence[str]] @@ -683,7 +683,7 @@ async def patch_skill( source: typing.Optional[str] = OMIT, url_pattern: typing.Optional[str] = OMIT, compatibility: typing.Optional[str] = OMIT, - files: typing.Optional[typing.Dict[str, typing.Optional[str]]] = OMIT, + files: typing.Optional[typing.Dict[str, str]] = OMIT, executable_files: typing.Optional[typing.Sequence[str]] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> Skill: @@ -704,7 +704,7 @@ async def patch_skill( compatibility : typing.Optional[str] - files : typing.Optional[typing.Dict[str, typing.Optional[str]]] + files : typing.Optional[typing.Dict[str, str]] executable_files : typing.Optional[typing.Sequence[str]] diff --git a/src/hai_agents/skills/raw_client.py b/src/hai_agents/skills/raw_client.py index 6808320..354fa30 100644 --- a/src/hai_agents/skills/raw_client.py +++ b/src/hai_agents/skills/raw_client.py @@ -403,7 +403,7 @@ def patch_skill( source: typing.Optional[str] = OMIT, url_pattern: typing.Optional[str] = OMIT, compatibility: typing.Optional[str] = OMIT, - files: typing.Optional[typing.Dict[str, typing.Optional[str]]] = OMIT, + files: typing.Optional[typing.Dict[str, str]] = OMIT, executable_files: typing.Optional[typing.Sequence[str]] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[Skill]: @@ -424,7 +424,7 @@ def patch_skill( compatibility : typing.Optional[str] - files : typing.Optional[typing.Dict[str, typing.Optional[str]]] + files : typing.Optional[typing.Dict[str, str]] executable_files : typing.Optional[typing.Sequence[str]] @@ -871,7 +871,7 @@ async def patch_skill( source: typing.Optional[str] = OMIT, url_pattern: typing.Optional[str] = OMIT, compatibility: typing.Optional[str] = OMIT, - files: typing.Optional[typing.Dict[str, typing.Optional[str]]] = OMIT, + files: typing.Optional[typing.Dict[str, str]] = OMIT, executable_files: typing.Optional[typing.Sequence[str]] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[Skill]: @@ -892,7 +892,7 @@ async def patch_skill( compatibility : typing.Optional[str] - files : typing.Optional[typing.Dict[str, typing.Optional[str]]] + files : typing.Optional[typing.Dict[str, str]] executable_files : typing.Optional[typing.Sequence[str]] diff --git a/src/hai_agents/types/__init__.py b/src/hai_agents/types/__init__.py index 6a9d2a9..97df9a5 100644 --- a/src/hai_agents/types/__init__.py +++ b/src/hai_agents/types/__init__.py @@ -28,6 +28,7 @@ AgentEventData_ToolResult, ) from .agent_reasoning_effort import AgentReasoningEffort + from .agent_revision_record import AgentRevisionRecord from .agent_run_status_change_data import AgentRunStatusChangeData from .agent_run_status_change_data_status import AgentRunStatusChangeDataStatus from .agent_run_status_change_event import AgentRunStatusChangeEvent @@ -35,6 +36,7 @@ from .agent_started_data import AgentStartedData from .agent_started_event import AgentStartedEvent from .agent_subagents_item import AgentSubagentsItem + from .agent_with_revision import AgentWithRevision from .android import Android from .answer_event import AnswerEvent from .answer_event_answer import AnswerEventAnswer @@ -93,6 +95,7 @@ from .one_password_config import OnePasswordConfig from .one_password_config_provider import OnePasswordConfigProvider from .page_agent import PageAgent + from .page_agent_revision_record import PageAgentRevisionRecord from .page_schedule_record import PageScheduleRecord from .page_schedule_run_record import PageScheduleRunRecord from .page_session_event import PageSessionEvent @@ -194,6 +197,7 @@ "AgentEventData_PolicyEvent": ".agent_event_data", "AgentEventData_ToolResult": ".agent_event_data", "AgentReasoningEffort": ".agent_reasoning_effort", + "AgentRevisionRecord": ".agent_revision_record", "AgentRunStatusChangeData": ".agent_run_status_change_data", "AgentRunStatusChangeDataStatus": ".agent_run_status_change_data_status", "AgentRunStatusChangeEvent": ".agent_run_status_change_event", @@ -201,6 +205,7 @@ "AgentStartedData": ".agent_started_data", "AgentStartedEvent": ".agent_started_event", "AgentSubagentsItem": ".agent_subagents_item", + "AgentWithRevision": ".agent_with_revision", "Android": ".android", "AnswerEvent": ".answer_event", "AnswerEventAnswer": ".answer_event_answer", @@ -259,6 +264,7 @@ "OnePasswordConfig": ".one_password_config", "OnePasswordConfigProvider": ".one_password_config_provider", "PageAgent": ".page_agent", + "PageAgentRevisionRecord": ".page_agent_revision_record", "PageScheduleRecord": ".page_schedule_record", "PageScheduleRunRecord": ".page_schedule_run_record", "PageSessionEvent": ".page_session_event", @@ -380,6 +386,7 @@ def __dir__(): "AgentEventData_PolicyEvent", "AgentEventData_ToolResult", "AgentReasoningEffort", + "AgentRevisionRecord", "AgentRunStatusChangeData", "AgentRunStatusChangeDataStatus", "AgentRunStatusChangeEvent", @@ -387,6 +394,7 @@ def __dir__(): "AgentStartedData", "AgentStartedEvent", "AgentSubagentsItem", + "AgentWithRevision", "Android", "AnswerEvent", "AnswerEventAnswer", @@ -445,6 +453,7 @@ def __dir__(): "OnePasswordConfig", "OnePasswordConfigProvider", "PageAgent", + "PageAgentRevisionRecord", "PageScheduleRecord", "PageScheduleRunRecord", "PageSessionEvent", diff --git a/src/hai_agents/types/agent_revision_record.py b/src/hai_agents/types/agent_revision_record.py new file mode 100644 index 0000000..f190778 --- /dev/null +++ b/src/hai_agents/types/agent_revision_record.py @@ -0,0 +1,43 @@ +# 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 AgentRevisionRecord(UniversalBaseModel): + """ + One stored revision of an agent, without its spec. + """ + + number: int = pydantic.Field() + """ + Revision number, from 1. Read its spec with ``GET /v2/agents/{agent_name}?revision={number}``. + """ + + created_at: dt.datetime + created_by: typing.Optional[str] = pydantic.Field(default=None) + """ + User who made the change. Null when unknown, and on reserved ``h/`` agents for non-H callers. + """ + + message: typing.Optional[str] = pydantic.Field(default=None) + """ + Note sent with the change, if any. + """ + + restored_from: typing.Optional[int] = pydantic.Field(default=None) + """ + The earlier revision whose spec this one restored, through ``POST .../revisions/{N}/restore``. + """ + + 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/agent_with_revision.py b/src/hai_agents/types/agent_with_revision.py new file mode 100644 index 0000000..d8acfa6 --- /dev/null +++ b/src/hai_agents/types/agent_with_revision.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2 +from .agent import Agent + + +class AgentWithRevision(Agent): + """ + An agent spec with its current revision number, as listed by ``GET /v2/agents?include=revision``. + + Only listed, never accepted as a body: ``revision`` is not part of the spec. + """ + + revision: int = pydantic.Field() + """ + Current revision number of the agent, as its ``ETag`` carries it. + """ + + 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/page_agent_revision_record.py b/src/hai_agents/types/page_agent_revision_record.py new file mode 100644 index 0000000..3c081d1 --- /dev/null +++ b/src/hai_agents/types/page_agent_revision_record.py @@ -0,0 +1,22 @@ +# 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 .agent_revision_record import AgentRevisionRecord + + +class PageAgentRevisionRecord(UniversalBaseModel): + items: typing.List[AgentRevisionRecord] + total: int + page: 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/src/hai_agents/types/session.py b/src/hai_agents/types/session.py index 2d81caa..468bf0d 100644 --- a/src/hai_agents/types/session.py +++ b/src/hai_agents/types/session.py @@ -17,8 +17,17 @@ class Session(UniversalBaseModel): """ id: str - request: SessionRequest + request: SessionRequest = pydantic.Field() + """ + The request the session runs. Secrets read back as ``***``, with keys and list lengths kept: MCP server ``headers``, ``env`` and ``args``, code sandbox ``env``, and browser ``proxy_url``. + """ + status: SessionStatus + agent_revision: typing.Optional[int] = pydantic.Field(default=None) + """ + Revision of the catalog agent the session runs; ``request.agent`` is that revision, resolved, with ``request.overrides`` applied. Null for inline agent specs and sessions created before agent revisions. + """ + agent_view_url: typing.Optional[str] = pydantic.Field(default=None) """ URL of the session's Agent View page on the H Platform (live view and replay). diff --git a/src/hai_agents/types/session_summary.py b/src/hai_agents/types/session_summary.py index 7a2e635..f603914 100644 --- a/src/hai_agents/types/session_summary.py +++ b/src/hai_agents/types/session_summary.py @@ -16,6 +16,11 @@ class SessionSummary(UniversalBaseModel): id: str agent: typing.Optional[str] = None + agent_revision: typing.Optional[int] = pydantic.Field(default=None) + """ + Revision of the catalog agent the session runs; null for inline specs and older sessions. + """ + status: TrajectoryStatus agent_view_url: typing.Optional[str] = pydantic.Field(default=None) """