diff --git a/package-lock.json b/package-lock.json index 95d450c..b1b1b8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,9 +17,9 @@ } }, "node_modules/@archastro/channel-harness": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@archastro/channel-harness/-/channel-harness-0.4.0.tgz", - "integrity": "sha512-9IlEXrxXtH5fANxmYkAy4UvqWVYkeazdyzw+flHBrBgSXtJsWNdvCQDp71Ug0ZnatA0Hy0ke9a3LzkIpHtGHjg==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@archastro/channel-harness/-/channel-harness-0.4.1.tgz", + "integrity": "sha512-lYnDmJIhtv00fn8Rl2wkpl7i9xi1dV5Oqx3mBUkS7iC9/nmDr+S7l5lD1KHXDee8NDYxnByjscXxAVZvpAwy6w==", "dev": true, "license": "MIT", "dependencies": { @@ -36,7 +36,7 @@ "node": ">=20" } }, - "node_modules/@archastro/sdk-generator": { + "node_modules/@archastro/channel-harness/node_modules/@archastro/sdk-generator": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@archastro/sdk-generator/-/sdk-generator-0.6.0.tgz", "integrity": "sha512-TCxPG8fSJDNQ0hWtWdFWZeT1pwVzp7b9Zll84LaSuba68yqHd2QJjj0Ykw5s/N3VCwia2tkQBKK7wDsOSH6PCA==", @@ -54,6 +54,24 @@ "node": ">=20" } }, + "node_modules/@archastro/sdk-generator": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@archastro/sdk-generator/-/sdk-generator-0.7.2.tgz", + "integrity": "sha512-za9mdb/7QAV9AFUQIgGGWaWIhZCh85I46IzN3czZsLTdOsXTBSPAaFjTE3e9pAldj3E5WFFt9YAwyGKvlev+2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "yaml": "^2.4.0" + }, + "bin": { + "sdk-generator": "dist/index.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@faker-js/faker": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-6.3.1.tgz", diff --git a/specs/platform-openapi.json b/specs/platform-openapi.json index 18c7a4b..31724b1 100644 --- a/specs/platform-openapi.json +++ b/specs/platform-openapi.json @@ -1,6 +1,180 @@ { "components": { "schemas": { + "AIChatStreamDone": { + "description": "Terminal event marking the end of a streaming chat completion (SSE `done` event).", + "example": { + "finish_reason": "stop", + "run_count": 1, + "total_usage": {}, + "usage": {} + }, + "properties": { + "finish_reason": { + "description": "The overall finish reason for the completion.", + "example": "stop", + "type": "string" + }, + "run_count": { + "description": "Number of model runs executed, including continuations triggered by tool calls.", + "example": 1, + "type": "integer" + }, + "total_usage": { + "description": "Aggregate token usage across every run in the completion (including tool-call continuations), keyed by model ID.", + "example": {}, + "type": "object" + }, + "usage": { + "description": "Token usage for the final run, keyed by model ID.", + "example": {}, + "type": "object" + } + }, + "type": "object" + }, + "AIChatStreamError": { + "description": "Terminal error event for a streaming chat completion (SSE `error` event).", + "example": { + "message": "string" + }, + "properties": { + "message": { + "description": "Human-readable description of the error that terminated the stream.", + "example": "string", + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "AIChatStreamMessageComplete": { + "description": "The fully assembled assistant message for one run of a streaming chat completion (SSE `message_complete` event).", + "example": { + "finish_reason": "stop", + "message": { + "content": "How can I help you today?", + "content_parts": [ + {} + ], + "resume_token": "string", + "role": "user", + "tool_calls": [ + { + "arguments": {}, + "id": "string", + "name": "Example Name", + "thought_signature": "string" + } + ], + "tool_results": [ + { + "content": "string", + "id": "string", + "name": "Example Name" + } + ] + }, + "usage": {} + }, + "properties": { + "finish_reason": { + "description": "Why the model stopped generating this message, e.g. `\"stop\"`, `\"length\"`, or `\"tool_calls\"`.", + "example": "stop", + "type": "string" + }, + "message": { + "$ref": "#/components/schemas/AIMessage", + "description": "The complete assistant message for this run, assembled from the preceding deltas." + }, + "usage": { + "description": "Token consumption for this run, keyed by model ID. `null` when usage data is unavailable.", + "example": {}, + "type": "object" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "AIChatStreamMessageDelta": { + "description": "Incremental assistant text emitted during a streaming chat completion (SSE `message_delta` event).", + "example": { + "delta": "string" + }, + "properties": { + "delta": { + "description": "The chunk of assistant text produced since the previous `message_delta` event. Concatenate deltas in order to reconstruct the message.", + "example": "string", + "type": "string" + } + }, + "required": [ + "delta" + ], + "type": "object" + }, + "AIChatStreamToolCallDelta": { + "description": "Incremental tool-call data emitted as the model assembles a tool invocation (SSE `tool_call_delta` event).", + "example": { + "delta": "string", + "id": "string", + "name": "Example Name" + }, + "properties": { + "delta": { + "description": "A chunk of the tool call's serialized arguments. Concatenate deltas to reconstruct the arguments JSON.", + "example": "string", + "type": "string" + }, + "id": { + "description": "Identifier of the tool call this delta belongs to, once the model has assigned one.", + "example": "string", + "type": "string" + }, + "name": { + "description": "Name of the tool being called, once known.", + "example": "Example Name", + "type": "string" + } + }, + "type": "object" + }, + "AIChatStreamToolResult": { + "description": "The result of a server-executed tool, streamed back into the run (SSE `tool_result` event).", + "example": { + "content": "string", + "id": "string", + "name": "Example Name", + "resolution": "string" + }, + "properties": { + "content": { + "description": "The tool's output, serialized as a string.", + "example": "string", + "type": "string" + }, + "id": { + "description": "ID of the tool call this result satisfies.", + "example": "string", + "type": "string" + }, + "name": { + "description": "Name of the tool that produced this result.", + "example": "Example Name", + "type": "string" + }, + "resolution": { + "description": "How the tool call resolved, e.g. `\"ok\"` or `\"error\"`.", + "example": "string", + "type": "string" + } + }, + "type": "object" + }, "AICompletionResult": { "description": "The result of an AI chat completion request. Returned by chat completion endpoints after the model finishes generating.", "example": { @@ -578,6 +752,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -1302,6 +1477,11 @@ "example": "tem_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "template_upgrade_available": { + "description": "True when the agent's last-applied template version is behind the current version of its AgentTemplate config — i.e. reapplying the template (a per-agent upgrade) would bring it newer Solution content. Self-clears once the agent is reapplied. Computed only on list endpoints; `null` on single-agent GET.", + "example": true, + "type": "boolean" + }, "updated_at": { "description": "When the agent was last modified (ISO 8601).", "example": "2024-01-01T00:00:00Z", @@ -1747,6 +1927,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -1842,6 +2023,11 @@ "example": "tem_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "template_upgrade_available": { + "description": "True when the agent's last-applied template version is behind the current version of its AgentTemplate config — i.e. reapplying the template (a per-agent upgrade) would bring it newer Solution content. Self-clears once the agent is reapplied. Computed only on list endpoints; `null` on single-agent GET.", + "example": true, + "type": "boolean" + }, "updated_at": { "description": "When the agent was last modified (ISO 8601).", "example": "2024-01-01T00:00:00Z", @@ -1876,6 +2062,7 @@ "key": "value" }, "name": "Example Name", + "provider": "sprites", "region": "us-east-1", "sprite_url": "https://example.com", "status": "ready", @@ -1938,8 +2125,13 @@ "example": "Example Name", "type": "string" }, + "provider": { + "description": "Compute backend powering this computer: `\"sprites\"` (Fly Sprites) or `\"vercel\"` (Vercel Sandbox).", + "example": "sprites", + "type": "string" + }, "region": { - "description": "Cloud region where the computer is hosted, e.g. `\"us-east-1\"`. `null` if not yet assigned.", + "description": "Cloud region where the computer is hosted, e.g. `\"us-east-1\"`. `null` if not yet assigned or when the provider has no region concept (e.g. `\"vercel\"`).", "example": "us-east-1", "type": "string" }, @@ -1984,6 +2176,7 @@ "key": "value" }, "name": "Example Name", + "provider": "sprites", "region": "us-east-1", "sprite_url": "https://example.com", "status": "ready", @@ -2425,6 +2618,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -2615,7 +2809,7 @@ "type": "string" }, "params": { - "description": "Kind-specific structured data used to construct the deep-link for this action. For `\"env_var\"` actions includes `key` and `scope`; for `\"install\"` actions includes `installation_kind`; for `\"integration\"` actions includes `mcp_server_ref`, and when resolvable also includes `provider` and `integration_id` for OAuth handoff. Empty object `{}` when no additional parameters are needed.", + "description": "Kind-specific structured data used to construct the deep-link for this action. For `\"env_var\"` actions includes `key` and `scope`; for `\"install\"` actions includes `installation_kind`; for `\"integration\"` actions includes `mcp_server_ref`, and when resolvable also includes `provider`, `integration_id` for OAuth handoff, and `connection_status` (`\"connected\"`, `\"disconnected\"`, or `\"token_expired\"`). Empty object `{}` when no additional parameters are needed.", "example": { "key": "value" }, @@ -2786,6 +2980,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -4256,6 +4451,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -5284,6 +5480,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -5404,6 +5601,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -5447,6 +5645,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -5629,6 +5828,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -5853,6 +6053,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -5969,6 +6170,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -6193,6 +6395,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -6351,6 +6554,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -6471,6 +6675,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -6514,6 +6719,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -6696,6 +6902,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -6920,6 +7127,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -7086,6 +7294,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -7156,6 +7365,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -7278,6 +7488,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -7492,6 +7703,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -7612,6 +7824,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -7655,6 +7868,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -7837,6 +8051,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -8061,6 +8276,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -9947,6 +10163,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -10044,6 +10261,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -10147,6 +10365,15 @@ "example": "agi_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "agent_mode": { + "description": "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path.", + "enum": [ + "cli", + "embedded" + ], + "example": "cli", + "type": "string" + }, "attachments": { "description": "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.", "items": { @@ -10259,6 +10486,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -10644,6 +10872,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -12832,6 +13061,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -13060,6 +13290,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -13206,6 +13437,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -13430,6 +13662,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -14508,6 +14741,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -15232,6 +15466,11 @@ "example": "tem_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "template_upgrade_available": { + "description": "True when the agent's last-applied template version is behind the current version of its AgentTemplate config — i.e. reapplying the template (a per-agent upgrade) would bring it newer Solution content. Self-clears once the agent is reapplied. Computed only on list endpoints; `null` on single-agent GET.", + "example": true, + "type": "boolean" + }, "updated_at": { "description": "When the agent was last modified (ISO 8601).", "example": "2024-01-01T00:00:00Z", @@ -20133,11 +20372,12 @@ "key": "value" }, "name": "Example Name", + "provider": "string", "region": "string" }, "properties": { "config": { - "description": "Provider-specific configuration for the computer. Supported keys vary by region and plan.", + "description": "Provider-specific configuration for the computer. Supported keys vary by provider. A top-level `provider` takes precedence over `config.provider`.", "example": {}, "type": "object" }, @@ -20158,6 +20398,11 @@ "example": "Example Name", "type": "string" }, + "provider": { + "description": "Compute backend for the computer: `\"sprites\"` (Fly Sprites, the default) or `\"vercel\"` (Vercel Sandbox). Folded into `config.provider`.", + "example": "string", + "type": "string" + }, "region": { "description": "Region in which to provision the computer, e.g. `\"iad\"`. Defaults to `\"iad\"` when omitted.", "example": "string", @@ -22404,7 +22649,7 @@ "description": "App-scoped token required. Use a token scoped to the target app.; Forbidden" }, "404": { - "description": "Agent not found; Template not found" + "description": "Agent not found; Template not found; This agent's template is no longer part of its Solution. The Solution may have been changed or re-synced since this agent was installed." }, "409": { "description": "Agent template changed since review; prepare the diff again." @@ -22831,6 +23076,456 @@ ] } }, + "/api/v1/ai/chat/completions/stream": { + "post": { + "description": "Streams a chat completion over Server-Sent Events. Emits `message_delta`,\n`message_complete`, `tool_call_*`, `tool_result`, and a terminal `done` (or\n`error`) event. Same request shape as the non-streaming completion endpoint;\nthe app must have the `llm_calls` entitlement.\n", + "operationId": "post_api_v1_ai_chat_completions_stream", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "example": { + "context": {}, + "messages": [ + { + "content": "How can I help you today?", + "content_parts": [ + {} + ], + "resume_token": "string", + "role": "user", + "tool_calls": [ + { + "arguments": {}, + "id": "string", + "name": "Example Name", + "thought_signature": "string" + } + ], + "tool_results": [ + { + "content": "string", + "id": "string", + "name": "Example Name" + } + ] + } + ], + "opts": { + "max_tokens": 1, + "model": "string", + "server_tools": [ + {} + ], + "temperature": 1.0, + "tool_choice": "string", + "tools": [ + { + "function": { + "description": "An example description.", + "name": "Example Name", + "parameters": {} + }, + "type": "function" + } + ] + } + }, + "properties": { + "context": { + "description": "Key-value map used to resolve template variables in message content. Omit if messages contain no templates.", + "example": {}, + "type": "object" + }, + "messages": { + "description": "Ordered list of conversation messages to send to the model.", + "example": [ + { + "content": "How can I help you today?", + "content_parts": [ + {} + ], + "resume_token": "string", + "role": "user", + "tool_calls": [ + { + "arguments": {}, + "id": "string", + "name": "Example Name", + "thought_signature": "string" + } + ], + "tool_results": [ + { + "content": "string", + "id": "string", + "name": "Example Name" + } + ] + } + ], + "items": { + "description": "A single message in an AI conversation, following the OpenAI-compatible chat format. Used in both request inputs and completion responses.", + "example": { + "content": "How can I help you today?", + "content_parts": [ + {} + ], + "resume_token": "string", + "role": "user", + "tool_calls": [ + { + "arguments": {}, + "id": "string", + "name": "Example Name", + "thought_signature": "string" + } + ], + "tool_results": [ + { + "content": "string", + "id": "string", + "name": "Example Name" + } + ] + }, + "properties": { + "content": { + "description": "Plain-text content of the message. Present for `system`, `user`, and `assistant` messages. `null` when the message body is expressed through `content_parts` or `tool_calls`.", + "example": "How can I help you today?", + "type": "string" + }, + "content_parts": { + "description": "Multimodal content parts for the message, used when the body includes images or mixed media. Each part is a map with a `type` key (`\"text\"`, `\"image_url\"`, or `\"image_data\"`). `null` when `content` is set.", + "example": [ + {} + ], + "items": { + "type": "object" + }, + "type": "array" + }, + "resume_token": { + "description": "Opaque token that can be passed on a subsequent request to resume this conversation from the current state. `null` when the provider does not support conversation resumption.", + "example": "string", + "type": "string" + }, + "role": { + "description": "The speaker role for this message. One of `\"system\"`, `\"user\"`, `\"assistant\"`, or `\"tool\"`.", + "example": "user", + "type": "string" + }, + "structured_output": { + "description": "Parsed structured data returned by the model when a JSON schema or structured-output mode was requested. Shape varies by the schema supplied at call time. `null` when structured output was not requested." + }, + "tool_calls": { + "description": "Tool calls requested by the model in an `assistant` message. Present only on assistant messages that invoke one or more tools. `null` on all other message roles.", + "example": [ + { + "arguments": {}, + "id": "string", + "name": "Example Name", + "thought_signature": "string" + } + ], + "items": { + "description": "A tool (function) call emitted by the assistant within an AI message. Mirrors the OpenAI tool-call object format.", + "example": { + "arguments": {}, + "id": "string", + "name": "Example Name", + "thought_signature": "string" + }, + "properties": { + "arguments": { + "description": "Arguments the model wants to pass to the tool, as a key-value map. Deserialize and validate these against the tool's input schema before executing.", + "example": {}, + "type": "object" + }, + "id": { + "description": "Unique identifier for this tool call, assigned by the model. Use this value as `id` when submitting the corresponding tool result.", + "example": "string", + "type": "string" + }, + "name": { + "description": "Name of the tool or function the model wants to invoke, e.g. `\"web_search\"` or `\"run_code\"`.", + "example": "Example Name", + "type": "string" + }, + "thought_signature": { + "description": "Opaque signature representing the model's internal reasoning that led to this tool call. `null` when the provider does not expose chain-of-thought data.", + "example": "string", + "type": "string" + } + }, + "required": [ + "id", + "name", + "arguments" + ], + "type": "object" + }, + "type": "array" + }, + "tool_results": { + "description": "Tool execution results provided in a `tool` message. Each entry corresponds to a prior tool call by its `id`. `null` on all other message roles.", + "example": [ + { + "content": "string", + "id": "string", + "name": "Example Name" + } + ], + "items": { + "description": "The result of executing a tool call, submitted back to the model as a tool-role message. Mirrors the OpenAI tool-result object format.", + "example": { + "content": "string", + "id": "string", + "name": "Example Name" + }, + "properties": { + "content": { + "description": "Plain-text output produced by the tool execution. `null` when the result is expressed entirely through `resolution`.", + "example": "string", + "type": "string" + }, + "id": { + "description": "ID of the tool call this result satisfies. Must match the `id` from the corresponding `AIToolCall`.", + "example": "string", + "type": "string" + }, + "name": { + "description": "Name of the tool or function that was executed, e.g. `\"web_search\"`. Must match the `name` from the corresponding `AIToolCall`.", + "example": "Example Name", + "type": "string" + }, + "resolution": { + "description": "Structured result data from the tool execution. Shape varies by tool. `null` when the result is expressed as plain text in `content`." + } + }, + "required": [ + "id", + "name" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "role" + ], + "type": "object" + }, + "type": "array" + }, + "opts": { + "description": "Model and sampling configuration for this request.", + "example": { + "max_tokens": 1, + "model": "string", + "server_tools": [ + {} + ], + "temperature": 1.0, + "tool_choice": "string", + "tools": [ + { + "function": { + "description": "An example description.", + "name": "Example Name", + "parameters": {} + }, + "type": "function" + } + ] + }, + "properties": { + "max_tokens": { + "description": "Maximum number of tokens the model may generate. Omit for the model default.", + "example": 1, + "type": "integer" + }, + "model": { + "description": "Model identifier, e.g. `\"gpt-4o\"` or `\"claude-3-7-sonnet-latest\"`.", + "example": "string", + "type": "string" + }, + "server_tools": { + "description": "Server-managed tool declarations executed before the response. Each entry must include a `type` key; currently only `\"search\"` is supported.", + "example": [ + {} + ], + "items": { + "type": "object" + }, + "type": "array" + }, + "temperature": { + "description": "Sampling temperature between `0.0` and `2.0`. Higher values produce more random output. Omit for the model default.", + "example": 1.0, + "type": "number" + }, + "tool_choice": { + "description": "Controls tool selection. One of `\"auto\"`, `\"required\"`, or `\"none\"`. Omit to let the model decide.", + "example": "string", + "type": "string" + }, + "tools": { + "description": "OpenAI-compatible tool definitions available to the model. Omit when not using function calling.", + "example": [ + { + "function": { + "description": "An example description.", + "name": "Example Name", + "parameters": {} + }, + "type": "function" + } + ], + "items": { + "description": "An AI tool definition passed to a model, following the OpenAI tool-calling schema.", + "example": { + "function": { + "description": "An example description.", + "name": "Example Name", + "parameters": {} + }, + "type": "function" + }, + "properties": { + "function": { + "description": "Callable function this tool exposes, including its name, description, and parameter schema.", + "example": { + "description": "An example description.", + "name": "Example Name", + "parameters": {} + }, + "properties": { + "description": { + "description": "Human-readable description of what the function does. The model uses this to decide when to call the function. `null` if not provided.", + "example": "An example description.", + "type": "string" + }, + "name": { + "description": "Unique name of the function that the model can invoke, e.g. `\"get_weather\"`.", + "example": "Example Name", + "type": "string" + }, + "parameters": { + "description": "JSON Schema object describing the function's accepted parameters. Must be a valid JSON Schema of type `\"object\"`.", + "example": {}, + "type": "object" + } + }, + "required": [ + "name", + "parameters" + ], + "type": "object" + }, + "type": { + "description": "The tool type. Currently always `\"function\"`.", + "example": "function", + "type": "string" + } + }, + "required": [ + "type", + "function" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "model" + ], + "type": "object" + } + }, + "required": [ + "messages", + "opts" + ], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "text/event-stream": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/AIChatStreamMessageDelta" + }, + { + "$ref": "#/components/schemas/AIChatStreamMessageComplete" + }, + { + "$ref": "#/components/schemas/AIChatStreamToolCallDelta" + }, + { + "$ref": "#/components/schemas/AIChatStreamToolResult" + }, + { + "$ref": "#/components/schemas/AIChatStreamDone" + }, + { + "$ref": "#/components/schemas/AIChatStreamError" + } + ] + } + } + }, + "description": "Server-Sent Events stream" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "402": { + "description": "Payment required — plan does not allow this feature" + } + }, + "summary": "Stream a chat completion", + "x-auth": [ + "publishable_key", + "bearer" + ], + "x-sdk-streaming": { + "events": { + "done": { + "$ref": "#/components/schemas/AIChatStreamDone" + }, + "error": { + "$ref": "#/components/schemas/AIChatStreamError" + }, + "message_complete": { + "$ref": "#/components/schemas/AIChatStreamMessageComplete" + }, + "message_delta": { + "$ref": "#/components/schemas/AIChatStreamMessageDelta" + }, + "tool_call_delta": { + "$ref": "#/components/schemas/AIChatStreamToolCallDelta" + }, + "tool_result": { + "$ref": "#/components/schemas/AIChatStreamToolResult" + } + }, + "type": "sse" + } + } + }, "/api/v1/ai/chat/models": { "get": { "description": "Returns the set of AI models that can be used with the chat completion and\nworkflow endpoints. The list reflects models currently enabled for the\nplatform and includes each model's identifier and whether it is the default.\n\nUse the `model` field from any entry in `data` as the value for\n`opts.model` when calling the completions or workflows endpoint.\n", @@ -22845,9 +23540,18 @@ "example": { "data": [ { + "capabilities": [ + "image" + ], "default": true, "id": "claude-sonnet-4-6", - "name": "Example Name" + "input_media_formats": [ + "string" + ], + "name": "Example Name", + "output_media_formats": [ + "string" + ] } ] }, @@ -22856,19 +23560,52 @@ "description": "Array of available model objects. At least one entry is always present.", "example": [ { + "capabilities": [ + "image" + ], "default": true, "id": "claude-sonnet-4-6", - "name": "Example Name" + "input_media_formats": [ + "string" + ], + "name": "Example Name", + "output_media_formats": [ + "string" + ] } ], "items": { "description": "An AI model available on the platform. Returned in model-listing responses so clients can populate model pickers and resolve the platform default.", "example": { + "capabilities": [ + "image" + ], "default": true, "id": "claude-sonnet-4-6", - "name": "Example Name" + "input_media_formats": [ + "string" + ], + "name": "Example Name", + "output_media_formats": [ + "string" + ] }, "properties": { + "capabilities": { + "description": "Machine-readable model capabilities. `\"image\"` marks image-input chat, `\"search\"` marks built-in web search, and `\"thinking\"` marks models with configurable reasoning. Empty when the catalog entry declares no special capabilities.", + "example": [ + "image" + ], + "items": { + "enum": [ + "image", + "search", + "thinking" + ], + "type": "string" + }, + "type": "array" + }, "default": { "description": "`true` for the model the platform selects when an agent has no `default_model` configured, or for the system-wide fallback in image-generation contexts. Exactly one entry in any given model list carries this flag.", "example": true, @@ -22879,16 +23616,39 @@ "example": "claude-sonnet-4-6", "type": "string" }, + "input_media_formats": { + "description": "MIME types accepted in chat `content_parts` image/file inputs for this model. For image-capable chat models this includes values such as `\"image/png\"`. Empty for text-only models.", + "example": [ + "string" + ], + "items": { + "type": "string" + }, + "type": "array" + }, "name": { "description": "Human-readable display label for this model, e.g. `\"Claude Sonnet 4.6\"` or `\"Gemini 3.5 Flash (thinking)\"`. Render this value directly in pickers rather than attempting to parse or transform `id`. Falls back to the `id` string when the catalog entry does not declare an explicit name.", "example": "Example Name", "type": "string" + }, + "output_media_formats": { + "description": "MIME types this model can emit as media in chat responses. Empty for text-output models, including image-understanding models that only return text.", + "example": [ + "string" + ], + "items": { + "type": "string" + }, + "type": "array" } }, "required": [ "id", "name", - "default" + "default", + "capabilities", + "input_media_formats", + "output_media_formats" ], "type": "object" }, @@ -23208,9 +23968,18 @@ "example": { "data": [ { + "capabilities": [ + "image" + ], "default": true, "id": "claude-sonnet-4-6", - "name": "Example Name" + "input_media_formats": [ + "string" + ], + "name": "Example Name", + "output_media_formats": [ + "string" + ] } ] }, @@ -23219,19 +23988,52 @@ "description": "Array of available image generation models, including their identifiers, human-readable names, and which one is the platform default.", "example": [ { + "capabilities": [ + "image" + ], "default": true, "id": "claude-sonnet-4-6", - "name": "Example Name" + "input_media_formats": [ + "string" + ], + "name": "Example Name", + "output_media_formats": [ + "string" + ] } ], "items": { "description": "An AI model available on the platform. Returned in model-listing responses so clients can populate model pickers and resolve the platform default.", "example": { + "capabilities": [ + "image" + ], "default": true, "id": "claude-sonnet-4-6", - "name": "Example Name" + "input_media_formats": [ + "string" + ], + "name": "Example Name", + "output_media_formats": [ + "string" + ] }, "properties": { + "capabilities": { + "description": "Machine-readable model capabilities. `\"image\"` marks image-input chat, `\"search\"` marks built-in web search, and `\"thinking\"` marks models with configurable reasoning. Empty when the catalog entry declares no special capabilities.", + "example": [ + "image" + ], + "items": { + "enum": [ + "image", + "search", + "thinking" + ], + "type": "string" + }, + "type": "array" + }, "default": { "description": "`true` for the model the platform selects when an agent has no `default_model` configured, or for the system-wide fallback in image-generation contexts. Exactly one entry in any given model list carries this flag.", "example": true, @@ -23242,16 +24044,39 @@ "example": "claude-sonnet-4-6", "type": "string" }, + "input_media_formats": { + "description": "MIME types accepted in chat `content_parts` image/file inputs for this model. For image-capable chat models this includes values such as `\"image/png\"`. Empty for text-only models.", + "example": [ + "string" + ], + "items": { + "type": "string" + }, + "type": "array" + }, "name": { "description": "Human-readable display label for this model, e.g. `\"Claude Sonnet 4.6\"` or `\"Gemini 3.5 Flash (thinking)\"`. Render this value directly in pickers rather than attempting to parse or transform `id`. Falls back to the `id` string when the catalog entry does not declare an explicit name.", "example": "Example Name", "type": "string" + }, + "output_media_formats": { + "description": "MIME types this model can emit as media in chat responses. Empty for text-output models, including image-understanding models that only return text.", + "example": [ + "string" + ], + "items": { + "type": "string" + }, + "type": "array" } }, "required": [ "id", "name", - "default" + "default", + "capabilities", + "input_media_formats", + "output_media_formats" ], "type": "object" }, @@ -24783,6 +25608,7 @@ "agent": "string", "change_description": "An example description.", "data": {}, + "data_encoding": "string", "kind": "string", "lookup_key": "string", "mime_type": "application/json", @@ -24813,6 +25639,11 @@ "example": {}, "type": "object" }, + "data_encoding": { + "description": "Encoding of `raw_content`. Omit or set `\"raw\"` for literal content; set `\"base64\"` when sending binary content such as images or PDFs in JSON.", + "example": "string", + "type": "string" + }, "kind": { "description": "Config kind that determines the schema and behavior of the config, e.g. `\"Agent\"` or `\"APITool\"`.", "example": "string", @@ -25787,6 +26618,8 @@ "schema": { "example": { "agent": "string", + "data": {}, + "data_encoding": "string", "kind": "string", "mime_type": "application/json", "raw_content": "string", @@ -25799,6 +26632,16 @@ "example": "string", "type": "string" }, + "data": { + "description": "Optional metadata used by kind-specific validation. File and Image configs require `data.name` when validating direct binary content.", + "example": {}, + "type": "object" + }, + "data_encoding": { + "description": "Encoding of `raw_content`. Omit or set `\"raw\"` for literal content; set `\"base64\"` when sending binary content such as images or PDFs in JSON.", + "example": "string", + "type": "string" + }, "kind": { "description": "Config kind whose schema the content is validated against, e.g. `\"Agent\"` or `\"APITool\"`.", "example": "string", @@ -26027,6 +26870,7 @@ "agent": "string", "change_description": "An example description.", "data": {}, + "data_encoding": "string", "expected_version": 1, "lookup_key": "string", "mime_type": "application/json", @@ -26055,6 +26899,11 @@ "example": {}, "type": "object" }, + "data_encoding": { + "description": "Encoding of `raw_content`. Omit or set `\"raw\"` for literal content; set `\"base64\"` when sending binary content such as images or PDFs in JSON.", + "example": "string", + "type": "string" + }, "expected_version": { "description": "Version number the caller expects to be current. If the config's actual current version does not match, the request returns 409 to signal a concurrent modification. Omit to skip optimistic locking.", "example": 1, @@ -30366,7 +31215,7 @@ }, "/api/v1/solutions": { "get": { - "description": "Returns a paginated list of Solutions visible to the authenticated caller,\nmerging two scopes: app-level Solutions (system-owned rows with no org\naffiliation, visible to all authenticated viewers in the app) and org-level\nSolutions (system-owned rows stamped with the viewer's org ID, included when\nthe viewer carries an org context).\n\nSolutions that appear under both scopes are deduplicated by their stable\n`solution_id` value. The merged entry's `owners` array lists every scope the\nSolution was found under (`\"system\"` and/or `\"org\"`). When the app-level copy\nhas a higher `solution_version` than the org-level copy, the response includes\n`upgrade_available: true` and `latest_version` so callers can prompt for an\nupgrade.\n", + "description": "Returns a paginated list of Solutions visible to the caller, merging two\nscopes: app-level Solutions (system-owned rows with no org affiliation,\nvisible to everyone — including unauthenticated callers — so they can power\nthe public catalog) and org-level Solutions (system-owned rows stamped with\nthe viewer's org ID, included when an authenticated viewer carries an org\ncontext). Unauthenticated callers resolve to an app-scoped anonymous viewer\nand therefore only ever see the app-level scope.\n\nSolutions that appear under both scopes are deduplicated by their stable\n`solution_id` value. The merged entry's `owners` array lists every scope the\nSolution was found under (`\"system\"` and/or `\"org\"`). When the app-level copy\nhas a higher `solution_version` than the org-level copy, the response includes\n`upgrade_available: true` and `latest_version` so callers can prompt for an\nupgrade.\n", "operationId": "get_api_v1_solutions", "parameters": [ { @@ -31123,7 +31972,7 @@ ] }, "get": { - "description": "Returns a single Solution identified by its config ID (`cfg_...`) or\n`lookup_key`, in the same summary shape the list endpoint emits. The response\nalways includes a freshly-minted `readme_url` — call this endpoint to refresh\nan expired README token without making any other state change.\n\nVisibility matches the list endpoint: app-level Solutions (no org affiliation)\nare visible to every authenticated viewer in the app; org-scoped Solutions are\nonly visible to viewers whose org context matches. Anything else returns 404.\n\nWhen the resolved Solution is org-scoped, the endpoint compares its\n`solution_version` against the matching app-level copy. If the app-level copy\nis at a higher version the response includes `upgrade_available: true` and\n`latest_version`. App-level resolutions always report `upgrade_available: false`.\n", + "description": "Returns a single Solution identified by its config ID (`cfg_...`) or\n`lookup_key`, in the same summary shape the list endpoint emits. The response\nalways includes a freshly-minted `readme_url` — call this endpoint to refresh\nan expired README token without making any other state change.\n\nVisibility matches the list endpoint: app-level Solutions (no org affiliation)\nare visible to everyone — including unauthenticated callers, so the public\ncatalog can render a Solution's detail page logged-out; org-scoped Solutions\nare only visible to authenticated viewers whose org context matches. Anything\nelse returns 404.\n\nWhen the resolved Solution is org-scoped, the endpoint compares its\n`solution_version` against the matching app-level copy. If the app-level copy\nis at a higher version the response includes `upgrade_available: true` and\n`latest_version`. App-level resolutions always report `upgrade_available: false`.\n", "operationId": "get_api_v1_solutions__solution", "parameters": [ { @@ -34484,6 +35333,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -34645,6 +35495,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -34803,6 +35654,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -34960,6 +35812,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -35684,6 +36537,11 @@ "example": "tem_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "template_upgrade_available": { + "description": "True when the agent's last-applied template version is behind the current version of its AgentTemplate config — i.e. reapplying the template (a per-agent upgrade) would bring it newer Solution content. Self-clears once the agent is reapplied. Computed only on list endpoints; `null` on single-agent GET.", + "example": true, + "type": "boolean" + }, "updated_at": { "description": "When the agent was last modified (ISO 8601).", "example": "2024-01-01T00:00:00Z", @@ -36160,6 +37018,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -36384,6 +37243,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -36461,6 +37321,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -36685,6 +37546,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -36759,6 +37621,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -36983,6 +37846,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -37186,6 +38050,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -37382,6 +38247,15 @@ "example": "agi_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "agent_mode": { + "description": "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path.", + "enum": [ + "cli", + "embedded" + ], + "example": "cli", + "type": "string" + }, "attachments": { "description": "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.", "example": [ @@ -37931,6 +38805,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -38296,6 +39171,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -38415,6 +39291,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -39139,6 +40016,11 @@ "example": "tem_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "template_upgrade_available": { + "description": "True when the agent's last-applied template version is behind the current version of its AgentTemplate config — i.e. reapplying the template (a per-agent upgrade) would bring it newer Solution content. Self-clears once the agent is reapplied. Computed only on list endpoints; `null` on single-agent GET.", + "example": true, + "type": "boolean" + }, "updated_at": { "description": "When the agent was last modified (ISO 8601).", "example": "2024-01-01T00:00:00Z", @@ -40804,7 +41686,7 @@ }, "/api/v1/threads/{thread}/messages": { "get": { - "description": "Returns a cursor-paginated list of messages belonging to the specified thread,\nordered from oldest to newest. Supply `before_cursor` or `after_cursor` (not both)\nto page through the result set; omit both to receive the most recent page.\n\nThe authenticated user must have access to the thread's owner (workspace or user).\nA 403 is returned if the thread exists but is not accessible to the caller; a 404\nis returned if the thread does not exist or is not visible to the authenticated user.\n\nPass `include_reply_counts: true` to annotate each message with the number of\nthreaded replies it has received. This adds a small amount of latency and should\nbe omitted when reply counts are not needed.\n", + "description": "Returns a cursor-paginated list of messages belonging to the specified thread,\nordered from oldest to newest. Supply `before_cursor`, `after_cursor`, or both\nto page through or bound the result set; omit both to receive the most recent page.\nSupply `anchor` and `direction` to fetch a window before, after, or around a\nspecific message. Use `anchor=last_matching&anchor_agent_mode=embedded` to\nresolve the anchor from the latest embedded-agent message, and add\n`anchor_agent` to scope that resolution to a single sender agent.\n\nThe authenticated user must have access to the thread's owner (workspace or user).\nA 403 is returned if the thread exists but is not accessible to the caller; a 404\nis returned if the thread does not exist or is not visible to the authenticated user.\n\nPass `include_reply_counts: true` to annotate each message with the number of\nthreaded replies it has received. This adds a small amount of latency and should\nbe omitted when reply counts are not needed.\n", "operationId": "get_api_v1_threads__thread_messages", "parameters": [ { @@ -40818,7 +41700,7 @@ } }, { - "description": "Opaque cursor returned in a previous response's `before_cursor` field. When provided, returns the page of messages immediately before that position. Cannot be combined with `after_cursor`.", + "description": "Opaque cursor returned in a previous response's `before_cursor` field. When provided, returns messages immediately before that position. May be combined with `after_cursor` to bound a range.", "example": "string", "in": "query", "name": "before_cursor", @@ -40828,7 +41710,7 @@ } }, { - "description": "Opaque cursor returned in a previous response's `after_cursor` field. When provided, returns the page of messages immediately after that position. Cannot be combined with `before_cursor`.", + "description": "Opaque cursor returned in a previous response's `after_cursor` field. When provided, returns messages immediately after that position. May be combined with `before_cursor` to bound a range.", "example": "string", "in": "query", "name": "after_cursor", @@ -40847,6 +41729,85 @@ "type": "integer" } }, + { + "description": "Message ID (`msg_...`) to use as a window anchor, or `last_matching` to resolve the anchor from the latest message matching the anchor filters. Cannot be combined with `before_cursor` or `after_cursor`.", + "example": "string", + "in": "query", + "name": "anchor", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Window direction relative to `anchor`. `before` returns older messages, `after` returns newer messages, and `around` returns messages on both sides. Defaults to `after` when `anchor` is supplied. `direction=around` cannot be combined with an explicit `limit`; use `before_limit` and `after_limit`.", + "example": "before", + "in": "query", + "name": "direction", + "required": false, + "schema": { + "enum": [ + "before", + "after", + "around" + ], + "type": "string" + } + }, + { + "description": "For `direction=around`, maximum number of messages older than the anchor. Defaults to 20.", + "example": 1, + "in": "query", + "name": "before_limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "For `direction=around`, maximum number of messages newer than the anchor. Defaults to 20.", + "example": 1, + "in": "query", + "name": "after_limit", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "description": "Whether to include the anchor message in a window response. Defaults to `true` for `direction=around`; ignored for ordinary cursor pagination and one-sided windows.", + "example": true, + "in": "query", + "name": "include_anchor", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "description": "When `anchor=last_matching`, resolve the anchor from the latest message with this local agent execution mode.", + "example": "cli", + "in": "query", + "name": "anchor_agent_mode", + "required": false, + "schema": { + "enum": [ + "cli", + "embedded" + ], + "type": "string" + } + }, + { + "description": "When `anchor=last_matching`, scope the anchor resolution to messages sent by this agent (`agi_...`). Combine with `anchor_agent_mode` to resolve the latest message from a specific agent in a given mode.", + "example": "string", + "in": "query", + "name": "anchor_agent", + "required": false, + "schema": { + "type": "string" + } + }, { "description": "When `true`, each message in the response is annotated with its threaded reply count. Defaults to `false`. Adds latency; omit when reply counts are not needed.", "example": true, @@ -40867,6 +41828,7 @@ "example": { "data": { "after_cursor": "string", + "anchor": "string", "before_cursor": "string", "messages": [ { @@ -40887,6 +41849,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -40980,6 +41943,7 @@ "description": "Pagination envelope containing the messages for this page along with cursors for adjacent pages.", "example": { "after_cursor": "string", + "anchor": "string", "before_cursor": "string", "messages": [ { @@ -41000,6 +41964,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -41093,6 +42058,11 @@ "example": "string", "type": "string" }, + "anchor": { + "description": "Message ID used as the anchor for a windowed query. `null` for ordinary cursor pagination.", + "example": "string", + "type": "string" + }, "before_cursor": { "description": "Opaque cursor to pass as `before` to retrieve the page of messages older than this result set. `null` when there are no earlier messages.", "example": "string", @@ -41119,6 +42089,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -41225,6 +42196,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -41322,6 +42294,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -41523,6 +42496,15 @@ "example": "agi_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "agent_mode": { + "description": "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path.", + "enum": [ + "cli", + "embedded" + ], + "example": "cli", + "type": "string" + }, "attachments": { "description": "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.", "example": [ @@ -42072,6 +43054,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -43453,6 +44436,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -43677,6 +44661,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -43754,6 +44739,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -43978,6 +44964,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -44052,6 +45039,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -44276,6 +45264,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -44479,6 +45468,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -44675,6 +45665,15 @@ "example": "agi_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "agent_mode": { + "description": "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path.", + "enum": [ + "cli", + "embedded" + ], + "example": "cli", + "type": "string" + }, "attachments": { "description": "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.", "example": [ @@ -45224,6 +46223,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -45589,6 +46589,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -45708,6 +46709,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -46432,6 +47434,11 @@ "example": "tem_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "template_upgrade_available": { + "description": "True when the agent's last-applied template version is behind the current version of its AgentTemplate config — i.e. reapplying the template (a per-agent upgrade) would bring it newer Solution content. Self-clears once the agent is reapplied. Computed only on list endpoints; `null` on single-agent GET.", + "example": true, + "type": "boolean" + }, "updated_at": { "description": "When the agent was last modified (ISO 8601).", "example": "2024-01-01T00:00:00Z", @@ -47375,6 +48382,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -48099,6 +49107,11 @@ "example": "tem_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "template_upgrade_available": { + "description": "True when the agent's last-applied template version is behind the current version of its AgentTemplate config — i.e. reapplying the template (a per-agent upgrade) would bring it newer Solution content. Self-clears once the agent is reapplied. Computed only on list endpoints; `null` on single-agent GET.", + "example": true, + "type": "boolean" + }, "updated_at": { "description": "When the agent was last modified (ISO 8601).", "example": "2024-01-01T00:00:00Z", @@ -48786,6 +49799,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -48906,6 +49920,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -48949,6 +49964,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -49131,6 +50147,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -49355,6 +50372,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -49471,6 +50489,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -49695,6 +50714,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -49834,6 +50854,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -49954,6 +50975,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -49997,6 +51019,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -50179,6 +51202,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -50403,6 +51427,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -50545,6 +51570,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -51269,6 +52295,11 @@ "example": "tem_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "template_upgrade_available": { + "description": "True when the agent's last-applied template version is behind the current version of its AgentTemplate config — i.e. reapplying the template (a per-agent upgrade) would bring it newer Solution content. Self-clears once the agent is reapplied. Computed only on list endpoints; `null` on single-agent GET.", + "example": true, + "type": "boolean" + }, "updated_at": { "description": "When the agent was last modified (ISO 8601).", "example": "2024-01-01T00:00:00Z", @@ -51413,6 +52444,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -51553,6 +52585,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -51692,6 +52725,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -52416,6 +53450,11 @@ "example": "tem_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "template_upgrade_available": { + "description": "True when the agent's last-applied template version is behind the current version of its AgentTemplate config — i.e. reapplying the template (a per-agent upgrade) would bring it newer Solution content. Self-clears once the agent is reapplied. Computed only on list endpoints; `null` on single-agent GET.", + "example": true, + "type": "boolean" + }, "updated_at": { "description": "When the agent was last modified (ISO 8601).", "example": "2024-01-01T00:00:00Z", @@ -52565,6 +53604,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -52671,6 +53711,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -52768,6 +53809,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -52969,6 +54011,15 @@ "example": "agi_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "agent_mode": { + "description": "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path.", + "enum": [ + "cli", + "embedded" + ], + "example": "cli", + "type": "string" + }, "attachments": { "description": "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.", "example": [ @@ -53518,6 +54569,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -53981,6 +55033,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -54205,6 +55258,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -54408,6 +55462,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -54604,6 +55659,15 @@ "example": "agi_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "agent_mode": { + "description": "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path.", + "enum": [ + "cli", + "embedded" + ], + "example": "cli", + "type": "string" + }, "attachments": { "description": "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.", "example": [ @@ -55153,6 +56217,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -55518,6 +56583,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -55637,6 +56703,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -56361,6 +57428,11 @@ "example": "tem_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "template_upgrade_available": { + "description": "True when the agent's last-applied template version is behind the current version of its AgentTemplate config — i.e. reapplying the template (a per-agent upgrade) would bring it newer Solution content. Self-clears once the agent is reapplied. Computed only on list endpoints; `null` on single-agent GET.", + "example": true, + "type": "boolean" + }, "updated_at": { "description": "When the agent was last modified (ISO 8601).", "example": "2024-01-01T00:00:00Z", @@ -56808,6 +57880,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -57032,6 +58105,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -57235,6 +58309,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -57431,6 +58506,15 @@ "example": "agi_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "agent_mode": { + "description": "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path.", + "enum": [ + "cli", + "embedded" + ], + "example": "cli", + "type": "string" + }, "attachments": { "description": "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.", "example": [ @@ -57980,6 +59064,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -58345,6 +59430,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -58464,6 +59550,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -59188,6 +60275,11 @@ "example": "tem_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "template_upgrade_available": { + "description": "True when the agent's last-applied template version is behind the current version of its AgentTemplate config — i.e. reapplying the template (a per-agent upgrade) would bring it newer Solution content. Self-clears once the agent is reapplied. Computed only on list endpoints; `null` on single-agent GET.", + "example": true, + "type": "boolean" + }, "updated_at": { "description": "When the agent was last modified (ISO 8601).", "example": "2024-01-01T00:00:00Z", @@ -59355,6 +60447,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -59464,6 +60557,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -59570,6 +60664,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -59667,6 +60762,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -59868,6 +60964,15 @@ "example": "agi_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "agent_mode": { + "description": "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path.", + "enum": [ + "cli", + "embedded" + ], + "example": "cli", + "type": "string" + }, "attachments": { "description": "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.", "example": [ @@ -60417,6 +61522,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -60687,6 +61793,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -60807,6 +61914,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -60850,6 +61958,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -61032,6 +62141,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -61256,6 +62366,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -61396,6 +62507,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -61516,6 +62628,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -61559,6 +62672,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -61741,6 +62855,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -61965,6 +63080,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -62107,6 +63223,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -62831,6 +63948,11 @@ "example": "tem_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "template_upgrade_available": { + "description": "True when the agent's last-applied template version is behind the current version of its AgentTemplate config — i.e. reapplying the template (a per-agent upgrade) would bring it newer Solution content. Self-clears once the agent is reapplied. Computed only on list endpoints; `null` on single-agent GET.", + "example": true, + "type": "boolean" + }, "updated_at": { "description": "When the agent was last modified (ISO 8601).", "example": "2024-01-01T00:00:00Z", @@ -62975,6 +64097,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -63115,6 +64238,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -63254,6 +64378,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -63978,6 +65103,11 @@ "example": "tem_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "template_upgrade_available": { + "description": "True when the agent's last-applied template version is behind the current version of its AgentTemplate config — i.e. reapplying the template (a per-agent upgrade) would bring it newer Solution content. Self-clears once the agent is reapplied. Computed only on list endpoints; `null` on single-agent GET.", + "example": true, + "type": "boolean" + }, "updated_at": { "description": "When the agent was last modified (ISO 8601).", "example": "2024-01-01T00:00:00Z", @@ -64127,6 +65257,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -64233,6 +65364,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -64330,6 +65462,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -64531,6 +65664,15 @@ "example": "agi_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "agent_mode": { + "description": "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path.", + "enum": [ + "cli", + "embedded" + ], + "example": "cli", + "type": "string" + }, "attachments": { "description": "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.", "example": [ @@ -65080,6 +66222,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -65543,6 +66686,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -65767,6 +66911,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -65970,6 +67115,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -66166,6 +67312,15 @@ "example": "agi_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "agent_mode": { + "description": "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path.", + "enum": [ + "cli", + "embedded" + ], + "example": "cli", + "type": "string" + }, "attachments": { "description": "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.", "example": [ @@ -66715,6 +67870,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -67080,6 +68236,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" } @@ -67199,6 +68356,7 @@ } }, "team": "tem_0aBcDeFgHiJkLmNoPqRsTu", + "template_upgrade_available": true, "updated_at": "2024-01-01T00:00:00Z", "user": "usr_0aBcDeFgHiJkLmNoPqRsTu" }, @@ -67923,6 +69081,11 @@ "example": "tem_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "template_upgrade_available": { + "description": "True when the agent's last-applied template version is behind the current version of its AgentTemplate config — i.e. reapplying the template (a per-agent upgrade) would bring it newer Solution content. Self-clears once the agent is reapplied. Computed only on list endpoints; `null` on single-agent GET.", + "example": true, + "type": "boolean" + }, "updated_at": { "description": "When the agent was last modified (ISO 8601).", "example": "2024-01-01T00:00:00Z", @@ -68095,6 +69258,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -68202,6 +69366,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -68398,6 +69563,15 @@ "example": "agi_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "agent_mode": { + "description": "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path.", + "enum": [ + "cli", + "embedded" + ], + "example": "cli", + "type": "string" + }, "attachments": { "description": "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.", "example": [ @@ -68947,6 +70121,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -69113,6 +70288,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -69220,6 +70396,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -69416,6 +70593,15 @@ "example": "agi_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "agent_mode": { + "description": "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path.", + "enum": [ + "cli", + "embedded" + ], + "example": "cli", + "type": "string" + }, "attachments": { "description": "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.", "example": [ @@ -69965,6 +71151,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -70198,6 +71385,30 @@ "properties": {}, "type": "object" } + }, + { + "description": "Signal that the current user has started or stopped typing in the thread", + "event": "api:chat:typing", + "params": { + "example": { + "is_typing": true + }, + "properties": { + "is_typing": { + "example": true, + "type": "boolean" + } + }, + "required": [ + "is_typing" + ], + "type": "object" + }, + "returns": { + "description": "Empty acknowledgement payload returned by channel message handlers that produce no data. The wire envelope is `{\"status\": \"ok\", \"response\": {}}`.", + "properties": {}, + "type": "object" + } } ], "name": "ApiChatChannel", @@ -70227,6 +71438,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -70343,6 +71555,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -70539,6 +71752,15 @@ "example": "agi_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "agent_mode": { + "description": "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path.", + "enum": [ + "cli", + "embedded" + ], + "example": "cli", + "type": "string" + }, "attachments": { "description": "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.", "example": [ @@ -71088,6 +72310,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -71232,6 +72455,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -71340,6 +72564,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -71536,6 +72761,15 @@ "example": "agi_0aBcDeFgHiJkLmNoPqRsTu", "type": "string" }, + "agent_mode": { + "description": "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path.", + "enum": [ + "cli", + "embedded" + ], + "example": "cli", + "type": "string" + }, "attachments": { "description": "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.", "example": [ @@ -72085,6 +73319,7 @@ } ], "agent": "agi_0aBcDeFgHiJkLmNoPqRsTu", + "agent_mode": "cli", "attachments": [ { "content_type": "application/json", @@ -72247,6 +73482,126 @@ }, "type": "object" } + }, + { + "description": "Broadcast when a participant (human or agent) starts or stops typing. Ephemeral; never persisted.", + "event": "typing", + "payload": { + "example": { + "actor": { + "alias": "alice", + "id": "user-usr_01j3k5m7n9p2r4s6t8v0w1x2", + "name": "Example Name", + "profile_picture": { + "file": "fil_0aBcDeFgHiJkLmNoPqRsTu", + "height": 600, + "media": "med_0aBcDeFgHiJkLmNoPqRsTu", + "mime_type": "application/json", + "refresh_url": "https://example.com", + "url": "https://example.com", + "width": 800 + } + }, + "is_typing": true, + "thread_id": "string" + }, + "properties": { + "actor": { + "description": "The entity that authored a message, either a human user or an agent.", + "example": { + "alias": "alice", + "id": "user-usr_01j3k5m7n9p2r4s6t8v0w1x2", + "name": "Example Name", + "profile_picture": { + "file": "fil_0aBcDeFgHiJkLmNoPqRsTu", + "height": 600, + "media": "med_0aBcDeFgHiJkLmNoPqRsTu", + "mime_type": "application/json", + "refresh_url": "https://example.com", + "url": "https://example.com", + "width": 800 + } + }, + "properties": { + "alias": { + "description": "Short handle or alias for the actor, used as an alternate display identifier. `null` if not configured.", + "example": "alice", + "type": "string" + }, + "id": { + "description": "Composite actor identifier. Format is `\"user-\"` for human users or `\"agent-\"` for agents.", + "example": "user-usr_01j3k5m7n9p2r4s6t8v0w1x2", + "type": "string" + }, + "name": { + "description": "Display name of the actor shown in the UI. `null` if no name is set.", + "example": "Example Name", + "type": "string" + }, + "profile_picture": { + "description": "Profile picture for the actor. `null` if the actor has no profile picture.", + "example": { + "file": "fil_0aBcDeFgHiJkLmNoPqRsTu", + "height": 600, + "media": "med_0aBcDeFgHiJkLmNoPqRsTu", + "mime_type": "application/json", + "refresh_url": "https://example.com", + "url": "https://example.com", + "width": 800 + }, + "properties": { + "file": { + "description": "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file.", + "example": "fil_0aBcDeFgHiJkLmNoPqRsTu", + "type": "string" + }, + "height": { + "description": "Height of the image in pixels. `null` if not known.", + "example": 600, + "type": "integer" + }, + "media": { + "description": "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity.", + "example": "med_0aBcDeFgHiJkLmNoPqRsTu", + "type": "string" + }, + "mime_type": { + "description": "MIME type of the image, e.g. `\"image/png\"` or `\"image/jpeg\"`. `null` if not known.", + "example": "application/json", + "type": "string" + }, + "refresh_url": { + "description": "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing.", + "example": "https://example.com", + "type": "string" + }, + "url": { + "description": "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires.", + "example": "https://example.com", + "type": "string" + }, + "width": { + "description": "Width of the image in pixels. `null` if not known.", + "example": 800, + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "is_typing": { + "example": true, + "type": "boolean" + }, + "thread_id": { + "example": "string", + "type": "string" + } + }, + "type": "object" + } } ], "x-auth": [ diff --git a/src/archastro/platform/channels/api_chat_channel.py b/src/archastro/platform/channels/api_chat_channel.py index 0232ebb..8c18bbb 100644 --- a/src/archastro/platform/channels/api_chat_channel.py +++ b/src/archastro/platform/channels/api_chat_channel.py @@ -1,10 +1,10 @@ # Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. # This file is auto-generated by @archastro/sdk-generator. Do not edit. -# Content hash: 8f62b37a4cec +# Content hash: 25ed606eeb04 from collections.abc import Callable from datetime import datetime -from typing import TYPE_CHECKING, Any, Required, TypedDict +from typing import TYPE_CHECKING, Any, Literal, Required, TypedDict if TYPE_CHECKING: from archastro.phx_channel.socket import Socket @@ -76,6 +76,12 @@ class ApiChatRemoveReactionInput(TypedDict): message_id: str +class ApiChatTypingInput(TypedDict): + "Signal that the current user has started or stopped typing in the thread" + + is_typing: bool + + class MessageAddedPayloadMessageActorsItemProfilePicture(TypedDict, total=False): file: str | None "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file." @@ -216,6 +222,8 @@ class MessageAddedPayloadMessage(TypedDict, total=False): "Resolved actor descriptors for the message sender, combining identity and display metadata. Always contains exactly one entry." agent: str | None "ID of the agent user that sent this message (`agi_...`). `null` for messages sent by human users." + agent_mode: Literal["cli", "embedded"] | None + "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path." attachments: list[MessageAddedPayloadMessageAttachmentsItem] | None "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments." branched_thread: str | None @@ -410,6 +418,8 @@ class MessageUpdatedPayloadMessage(TypedDict, total=False): "Resolved actor descriptors for the message sender, combining identity and display metadata. Always contains exactly one entry." agent: str | None "ID of the agent user that sent this message (`agi_...`). `null` for messages sent by human users." + agent_mode: Literal["cli", "embedded"] | None + "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path." attachments: list[MessageUpdatedPayloadMessageAttachmentsItem] | None "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments." branched_thread: str | None @@ -476,6 +486,43 @@ class SystemEventPayload(TypedDict, total=False): event: dict[str, Any] | None +class TypingPayloadActorProfilePicture(TypedDict, total=False): + file: str | None + "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file." + height: int | None + "Height of the image in pixels. `null` if not known." + media: str | None + "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity." + mime_type: str | None + 'MIME type of the image, e.g. `"image/png"` or `"image/jpeg"`. `null` if not known.' + refresh_url: str | None + "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing." + url: str | None + "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires." + width: int | None + "Width of the image in pixels. `null` if not known." + + +class TypingPayloadActor(TypedDict, total=False): + alias: str | None + "Short handle or alias for the actor, used as an alternate display identifier. `null` if not configured." + id: str | None + 'Composite actor identifier. Format is `"user-"` for human users or `"agent-"` for agents.' + name: str | None + "Display name of the actor shown in the UI. `null` if no name is set." + profile_picture: TypingPayloadActorProfilePicture | None + "Profile picture for the actor. `null` if the actor has no profile picture." + + +class TypingPayload(TypedDict, total=False): + "Broadcast when a participant (human or agent) starts or stops typing. Ephemeral; never persisted." + + actor: TypingPayloadActor | None + "The entity that authored a message, either a human user or an agent." + is_typing: bool | None + thread_id: str | None + + # Channel for real-time chat messaging. # Supports team-scoped and user-scoped threads with keyed, transient, and direct # thread access patterns. @@ -723,6 +770,10 @@ async def api_chat_add_reaction(self, payload: ApiChatAddReactionInput) -> dict[ async def api_chat_remove_reaction(self, payload: ApiChatRemoveReactionInput) -> dict[str, Any]: return await self._channel.push("api:chat:remove_reaction", payload) + # Signal that the current user has started or stopped typing in the thread + async def api_chat_typing(self, payload: ApiChatTypingInput) -> dict[str, Any]: + return await self._channel.push("api:chat:typing", payload) + # Broadcast when a new message is added to a thread def on_message_added( self, callback: Callable[[MessageAddedPayload], None] @@ -742,3 +793,7 @@ def on_thread_event(self, callback: Callable[[ThreadEventPayload], None]) -> Cal # Broadcast system-wide events def on_system_event(self, callback: Callable[[SystemEventPayload], None]) -> Callable[[], None]: return self._channel.on("system_event", callback) + + # Broadcast when a participant (human or agent) starts or stops typing. Ephemeral; never persisted. + def on_typing(self, callback: Callable[[TypingPayload], None]) -> Callable[[], None]: + return self._channel.on("typing", callback) diff --git a/src/archastro/platform/types/ai.py b/src/archastro/platform/types/ai.py index 13f47ce..a210d05 100644 --- a/src/archastro/platform/types/ai.py +++ b/src/archastro/platform/types/ai.py @@ -1,12 +1,43 @@ # Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. # This file is auto-generated by @archastro/sdk-generator. Do not edit. -# Content hash: a33c192fd32c +# Content hash: c92e31a74295 from typing import Any from pydantic import BaseModel, Field +class AIChatStreamDone(BaseModel): + """ + Terminal event marking the end of a streaming chat completion (SSE `done` event). + """ + + finish_reason: str | None = Field( + default=None, description="The overall finish reason for the completion." + ) + run_count: int | None = Field( + default=None, + description="Number of model runs executed, including continuations triggered by tool calls.", + ) + total_usage: dict[str, Any] | None = Field( + default=None, + description="Aggregate token usage across every run in the completion (including tool-call continuations), keyed by model ID.", + ) + usage: dict[str, Any] | None = Field( + default=None, description="Token usage for the final run, keyed by model ID." + ) + + +class AIChatStreamError(BaseModel): + """ + Terminal error event for a streaming chat completion (SSE `error` event). + """ + + message: str = Field( + ..., description="Human-readable description of the error that terminated the stream." + ) + + class AIToolCall(BaseModel): """ A tool (function) call emitted by the assistant within an AI message. Mirrors the OpenAI tool-call object format. @@ -88,6 +119,69 @@ class AIMessage(BaseModel): ) +class AIChatStreamMessageComplete(BaseModel): + """ + The fully assembled assistant message for one run of a streaming chat completion (SSE `message_complete` event). + """ + + finish_reason: str | None = Field( + default=None, + description='Why the model stopped generating this message, e.g. `"stop"`, `"length"`, or `"tool_calls"`.', + ) + message: AIMessage = Field( + ..., + description="The complete assistant message for this run, assembled from the preceding deltas.", + ) + usage: dict[str, Any] | None = Field( + default=None, + description="Token consumption for this run, keyed by model ID. `null` when usage data is unavailable.", + ) + + +class AIChatStreamMessageDelta(BaseModel): + """ + Incremental assistant text emitted during a streaming chat completion (SSE `message_delta` event). + """ + + delta: str = Field( + ..., + description="The chunk of assistant text produced since the previous `message_delta` event. Concatenate deltas in order to reconstruct the message.", + ) + + +class AIChatStreamToolCallDelta(BaseModel): + """ + Incremental tool-call data emitted as the model assembles a tool invocation (SSE `tool_call_delta` event). + """ + + delta: str | None = Field( + default=None, + description="A chunk of the tool call's serialized arguments. Concatenate deltas to reconstruct the arguments JSON.", + ) + id: str | None = Field( + default=None, + description="Identifier of the tool call this delta belongs to, once the model has assigned one.", + ) + name: str | None = Field(default=None, description="Name of the tool being called, once known.") + + +class AIChatStreamToolResult(BaseModel): + """ + The result of a server-executed tool, streamed back into the run (SSE `tool_result` event). + """ + + content: str | None = Field( + default=None, description="The tool's output, serialized as a string." + ) + id: str | None = Field(default=None, description="ID of the tool call this result satisfies.") + name: str | None = Field( + default=None, description="Name of the tool that produced this result." + ) + resolution: str | None = Field( + default=None, description='How the tool call resolved, e.g. `"ok"` or `"error"`.' + ) + + class AICompletionResult(BaseModel): """ The result of an AI chat completion request. Returned by chat completion endpoints after the model finishes generating. diff --git a/src/archastro/platform/types/common.py b/src/archastro/platform/types/common.py index 1891c4f..b48707a 100644 --- a/src/archastro/platform/types/common.py +++ b/src/archastro/platform/types/common.py @@ -1,9 +1,9 @@ # Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. # This file is auto-generated by @archastro/sdk-generator. Do not edit. -# Content hash: 0308db13b588 +# Content hash: dc1810989af9 from datetime import datetime -from typing import Any +from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field @@ -602,6 +602,10 @@ class Message(BaseModel): default=None, description="ID of the agent user that sent this message (`agi_...`). `null` for messages sent by human users.", ) + agent_mode: Literal["cli", "embedded"] | None = Field( + default=None, + description="Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path.", + ) attachments: list[Attachment] | None = Field( default=None, description="Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.", @@ -1021,7 +1025,7 @@ class AgentHealthAction(BaseModel): ) params: dict[str, Any] | None = Field( default=None, - description='Kind-specific structured data used to construct the deep-link for this action. For `"env_var"` actions includes `key` and `scope`; for `"install"` actions includes `installation_kind`; for `"integration"` actions includes `mcp_server_ref`, and when resolvable also includes `provider` and `integration_id` for OAuth handoff. Empty object `{}` when no additional parameters are needed.', + description='Kind-specific structured data used to construct the deep-link for this action. For `"env_var"` actions includes `key` and `scope`; for `"install"` actions includes `installation_kind`; for `"integration"` actions includes `mcp_server_ref`, and when resolvable also includes `provider`, `integration_id` for OAuth handoff, and `connection_status` (`"connected"`, `"disconnected"`, or `"token_expired"`). Empty object `{}` when no additional parameters are needed.', ) required: bool = Field( ..., @@ -1861,6 +1865,10 @@ class Agent(BaseModel): default=None, description="ID of the team that owns this agent (`tem_...`). `null` if the agent is not team-scoped.", ) + template_upgrade_available: bool | None = Field( + default=None, + description="True when the agent's last-applied template version is behind the current version of its AgentTemplate config i.e. reapplying the template (a per-agent upgrade) would bring it newer Solution content. Self-clears once the agent is reapplied. Computed only on list endpoints; `null` on single-agent GET.", + ) updated_at: datetime | None = Field( default=None, description="When the agent was last modified (ISO 8601)." ) @@ -1909,9 +1917,13 @@ class AgentComputer(BaseModel): name: str | None = Field( default=None, description="Human-readable display name for the computer. `null` if not set." ) + provider: str | None = Field( + default=None, + description='Compute backend powering this computer: `"sprites"` (Fly Sprites) or `"vercel"` (Vercel Sandbox).', + ) region: str | None = Field( default=None, - description='Cloud region where the computer is hosted, e.g. `"us-east-1"`. `null` if not yet assigned.', + description='Cloud region where the computer is hosted, e.g. `"us-east-1"`. `null` if not yet assigned or when the provider has no region concept (e.g. `"vercel"`).', ) sprite_url: str | None = Field( default=None, diff --git a/src/archastro/platform/v1/resources/agents.py b/src/archastro/platform/v1/resources/agents.py index dfcbaad..04e321d 100644 --- a/src/archastro/platform/v1/resources/agents.py +++ b/src/archastro/platform/v1/resources/agents.py @@ -1,6 +1,6 @@ # Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. # This file is auto-generated by @archastro/sdk-generator. Do not edit. -# Content hash: 5121fa1a8576 +# Content hash: 4f1a14996b49 from __future__ import annotations @@ -39,13 +39,15 @@ class AgentAgentComputerCreateInput(TypedDict, total=False): "Provision a computer for an agent" config: dict[str, Any] | None - "Provider-specific configuration for the computer. Supported keys vary by region and plan." + "Provider-specific configuration for the computer. Supported keys vary by provider. A top-level `provider` takes precedence over `config.provider`." lookup_key: str | None "Stable, user-defined key for this computer. Must be unique within the app. Use it to look up the computer without storing its ID." metadata: dict[str, Any] | None "Arbitrary key-value metadata to attach to the computer. Not interpreted by the platform; returned as-is on all subsequent reads." name: Required[str] "Human-readable display name for the computer." + provider: str | None + 'Compute backend for the computer: `"sprites"` (Fly Sprites, the default) or `"vercel"` (Vercel Sandbox). Folded into `config.provider`.' region: str | None 'Region in which to provision the computer, e.g. `"iad"`. Defaults to `"iad"` when omitted.' @@ -670,10 +672,11 @@ async def create(self, agent: str, input: AgentAgentComputerCreateInput) -> Agen Args: agent: Agent ID (`agt_...`). When provided, only computers belonging to this agent are returned. input: Request body. - input.config: Provider-specific configuration for the computer. Supported keys vary by region and plan. + input.config: Provider-specific configuration for the computer. Supported keys vary by provider. A top-level `provider` takes precedence over `config.provider`. input.lookup_key: Stable, user-defined key for this computer. Must be unique within the app. Use it to look up the computer without storing its ID. input.metadata: Arbitrary key-value metadata to attach to the computer. Not interpreted by the platform; returned as-is on all subsequent reads. input.name: Human-readable display name for the computer. + input.provider: Compute backend for the computer: `"sprites"` (Fly Sprites, the default) or `"vercel"` (Vercel Sandbox). Folded into `config.provider`. input.region: Region in which to provision the computer, e.g. `"iad"`. Defaults to `"iad"` when omitted. Returns: @@ -1451,10 +1454,11 @@ def create(self, agent: str, input: AgentAgentComputerCreateInput) -> AgentCompu Args: agent: Agent ID (`agt_...`). When provided, only computers belonging to this agent are returned. input: Request body. - input.config: Provider-specific configuration for the computer. Supported keys vary by region and plan. + input.config: Provider-specific configuration for the computer. Supported keys vary by provider. A top-level `provider` takes precedence over `config.provider`. input.lookup_key: Stable, user-defined key for this computer. Must be unique within the app. Use it to look up the computer without storing its ID. input.metadata: Arbitrary key-value metadata to attach to the computer. Not interpreted by the platform; returned as-is on all subsequent reads. input.name: Human-readable display name for the computer. + input.provider: Compute backend for the computer: `"sprites"` (Fly Sprites, the default) or `"vercel"` (Vercel Sandbox). Folded into `config.provider`. input.region: Region in which to provision the computer, e.g. `"iad"`. Defaults to `"iad"` when omitted. Returns: diff --git a/src/archastro/platform/v1/resources/ai.py b/src/archastro/platform/v1/resources/ai.py index fa61cea..70e2a44 100644 --- a/src/archastro/platform/v1/resources/ai.py +++ b/src/archastro/platform/v1/resources/ai.py @@ -1,18 +1,28 @@ # Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. # This file is auto-generated by @archastro/sdk-generator. Do not edit. -# Content hash: e7c1a6ebd84a +# Content hash: 7fb31d670565 from __future__ import annotations -from typing import Any, Required, TypedDict +from collections.abc import AsyncIterator, Iterator +from typing import Any, Literal, Required, TypedDict from pydantic import BaseModel, Field from ...runtime.http_client import HttpClient, SyncHttpClient -from ...types.ai import AICompletionResult, AIImageResult - - -class ChatCompletionsInputMessagesItemToolCallsItem(TypedDict, total=False): +from ...types.ai import ( + AIChatStreamDone, + AIChatStreamError, + AIChatStreamMessageComplete, + AIChatStreamMessageDelta, + AIChatStreamToolCallDelta, + AIChatStreamToolResult, + AICompletionResult, + AIImageResult, +) + + +class CompletionCreateInputMessagesItemToolCallsItem(TypedDict, total=False): arguments: Required[dict[str, Any]] "Arguments the model wants to pass to the tool, as a key-value map. Deserialize and validate these against the tool's input schema before executing." id: Required[str] @@ -23,7 +33,7 @@ class ChatCompletionsInputMessagesItemToolCallsItem(TypedDict, total=False): "Opaque signature representing the model's internal reasoning that led to this tool call. `null` when the provider does not expose chain-of-thought data." -class ChatCompletionsInputMessagesItemToolResultsItem(TypedDict, total=False): +class CompletionCreateInputMessagesItemToolResultsItem(TypedDict, total=False): content: str | None "Plain-text output produced by the tool execution. `null` when the result is expressed entirely through `resolution`." id: Required[str] @@ -34,7 +44,7 @@ class ChatCompletionsInputMessagesItemToolResultsItem(TypedDict, total=False): "Structured result data from the tool execution. Shape varies by tool. `null` when the result is expressed as plain text in `content`." -class ChatCompletionsInputMessagesItem(TypedDict, total=False): +class CompletionCreateInputMessagesItem(TypedDict, total=False): content: str | None "Plain-text content of the message. Present for `system`, `user`, and `assistant` messages. `null` when the message body is expressed through `content_parts` or `tool_calls`." content_parts: list[dict[str, Any]] | None @@ -45,13 +55,13 @@ class ChatCompletionsInputMessagesItem(TypedDict, total=False): 'The speaker role for this message. One of `"system"`, `"user"`, `"assistant"`, or `"tool"`.' structured_output: Any | None "Parsed structured data returned by the model when a JSON schema or structured-output mode was requested. Shape varies by the schema supplied at call time. `null` when structured output was not requested." - tool_calls: list[ChatCompletionsInputMessagesItemToolCallsItem] | None + tool_calls: list[CompletionCreateInputMessagesItemToolCallsItem] | None "Tool calls requested by the model in an `assistant` message. Present only on assistant messages that invoke one or more tools. `null` on all other message roles." - tool_results: list[ChatCompletionsInputMessagesItemToolResultsItem] | None + tool_results: list[CompletionCreateInputMessagesItemToolResultsItem] | None "Tool execution results provided in a `tool` message. Each entry corresponds to a prior tool call by its `id`. `null` on all other message roles." -class ChatCompletionsInputOptsToolsItemFunction(TypedDict, total=False): +class CompletionCreateInputOptsToolsItemFunction(TypedDict, total=False): description: str | None "Human-readable description of what the function does. The model uses this to decide when to call the function. `null` if not provided." name: Required[str] @@ -60,14 +70,14 @@ class ChatCompletionsInputOptsToolsItemFunction(TypedDict, total=False): 'JSON Schema object describing the function\'s accepted parameters. Must be a valid JSON Schema of type `"object"`.' -class ChatCompletionsInputOptsToolsItem(TypedDict): - function: ChatCompletionsInputOptsToolsItemFunction +class CompletionCreateInputOptsToolsItem(TypedDict): + function: CompletionCreateInputOptsToolsItemFunction "Callable function this tool exposes, including its name, description, and parameter schema." type: str 'The tool type. Currently always `"function"`.' -class ChatCompletionsInputOpts(TypedDict, total=False): +class CompletionCreateInputOpts(TypedDict, total=False): max_tokens: int | None "Maximum number of tokens the model may generate in the completion. Omit to use the model's default limit." model: Required[str] @@ -78,18 +88,99 @@ class ChatCompletionsInputOpts(TypedDict, total=False): "Sampling temperature between `0.0` and `2.0`. Higher values produce more random output. Omit to use the model's default." tool_choice: str | None 'Controls how the model selects tools. One of `"auto"`, `"required"`, or `"none"`. Omit to let the model decide.' - tools: list[ChatCompletionsInputOptsToolsItem] | None + tools: list[CompletionCreateInputOptsToolsItem] | None "OpenAI-compatible tool definitions available to the model. Omit when not using function calling." -class ChatCompletionsInput(TypedDict, total=False): +class CompletionCreateInput(TypedDict, total=False): "Create a chat completion" context: dict[str, Any] | None "Key-value map used to resolve template variables in message content. Omit if messages contain no templates." - messages: Required[list[ChatCompletionsInputMessagesItem]] + messages: Required[list[CompletionCreateInputMessagesItem]] "Ordered list of conversation messages to send to the model." - opts: Required[ChatCompletionsInputOpts] + opts: Required[CompletionCreateInputOpts] + "Model and sampling configuration for this request." + + +class CompletionStreamInputMessagesItemToolCallsItem(TypedDict, total=False): + arguments: Required[dict[str, Any]] + "Arguments the model wants to pass to the tool, as a key-value map. Deserialize and validate these against the tool's input schema before executing." + id: Required[str] + "Unique identifier for this tool call, assigned by the model. Use this value as `id` when submitting the corresponding tool result." + name: Required[str] + 'Name of the tool or function the model wants to invoke, e.g. `"web_search"` or `"run_code"`.' + thought_signature: str | None + "Opaque signature representing the model's internal reasoning that led to this tool call. `null` when the provider does not expose chain-of-thought data." + + +class CompletionStreamInputMessagesItemToolResultsItem(TypedDict, total=False): + content: str | None + "Plain-text output produced by the tool execution. `null` when the result is expressed entirely through `resolution`." + id: Required[str] + "ID of the tool call this result satisfies. Must match the `id` from the corresponding `AIToolCall`." + name: Required[str] + 'Name of the tool or function that was executed, e.g. `"web_search"`. Must match the `name` from the corresponding `AIToolCall`.' + resolution: Any | None + "Structured result data from the tool execution. Shape varies by tool. `null` when the result is expressed as plain text in `content`." + + +class CompletionStreamInputMessagesItem(TypedDict, total=False): + content: str | None + "Plain-text content of the message. Present for `system`, `user`, and `assistant` messages. `null` when the message body is expressed through `content_parts` or `tool_calls`." + content_parts: list[dict[str, Any]] | None + 'Multimodal content parts for the message, used when the body includes images or mixed media. Each part is a map with a `type` key (`"text"`, `"image_url"`, or `"image_data"`). `null` when `content` is set.' + resume_token: str | None + "Opaque token that can be passed on a subsequent request to resume this conversation from the current state. `null` when the provider does not support conversation resumption." + role: Required[str] + 'The speaker role for this message. One of `"system"`, `"user"`, `"assistant"`, or `"tool"`.' + structured_output: Any | None + "Parsed structured data returned by the model when a JSON schema or structured-output mode was requested. Shape varies by the schema supplied at call time. `null` when structured output was not requested." + tool_calls: list[CompletionStreamInputMessagesItemToolCallsItem] | None + "Tool calls requested by the model in an `assistant` message. Present only on assistant messages that invoke one or more tools. `null` on all other message roles." + tool_results: list[CompletionStreamInputMessagesItemToolResultsItem] | None + "Tool execution results provided in a `tool` message. Each entry corresponds to a prior tool call by its `id`. `null` on all other message roles." + + +class CompletionStreamInputOptsToolsItemFunction(TypedDict, total=False): + description: str | None + "Human-readable description of what the function does. The model uses this to decide when to call the function. `null` if not provided." + name: Required[str] + 'Unique name of the function that the model can invoke, e.g. `"get_weather"`.' + parameters: Required[dict[str, Any]] + 'JSON Schema object describing the function\'s accepted parameters. Must be a valid JSON Schema of type `"object"`.' + + +class CompletionStreamInputOptsToolsItem(TypedDict): + function: CompletionStreamInputOptsToolsItemFunction + "Callable function this tool exposes, including its name, description, and parameter schema." + type: str + 'The tool type. Currently always `"function"`.' + + +class CompletionStreamInputOpts(TypedDict, total=False): + max_tokens: int | None + "Maximum number of tokens the model may generate. Omit for the model default." + model: Required[str] + 'Model identifier, e.g. `"gpt-4o"` or `"claude-3-7-sonnet-latest"`.' + server_tools: list[dict[str, Any]] | None + 'Server-managed tool declarations executed before the response. Each entry must include a `type` key; currently only `"search"` is supported.' + temperature: float | None + "Sampling temperature between `0.0` and `2.0`. Higher values produce more random output. Omit for the model default." + tool_choice: str | None + 'Controls tool selection. One of `"auto"`, `"required"`, or `"none"`. Omit to let the model decide.' + tools: list[CompletionStreamInputOptsToolsItem] | None + "OpenAI-compatible tool definitions available to the model. Omit when not using function calling." + + +class CompletionStreamInput(TypedDict, total=False): + "Stream a chat completion" + + context: dict[str, Any] | None + "Key-value map used to resolve template variables in message content. Omit if messages contain no templates." + messages: Required[list[CompletionStreamInputMessagesItem]] + "Ordered list of conversation messages to send to the model." + opts: Required[CompletionStreamInputOpts] "Model and sampling configuration for this request." @@ -159,6 +250,10 @@ class ImageGenerationsInput(TypedDict, total=False): class ChatModelsResponseDataItem(BaseModel): + capabilities: list[Literal["image", "search", "thinking"]] = Field( + ..., + description='Machine-readable model capabilities. `"image"` marks image-input chat, `"search"` marks built-in web search, and `"thinking"` marks models with configurable reasoning. Empty when the catalog entry declares no special capabilities.', + ) default: bool = Field( ..., description="`true` for the model the platform selects when an agent has no `default_model` configured, or for the system-wide fallback in image-generation contexts. Exactly one entry in any given model list carries this flag.", @@ -167,10 +262,18 @@ class ChatModelsResponseDataItem(BaseModel): ..., description='Provider-assigned model identifier used when specifying a model on API requests, e.g. `"claude-sonnet-4-6"` or `"gemini-2.5-flash"`.', ) + input_media_formats: list[str] = Field( + ..., + description='MIME types accepted in chat `content_parts` image/file inputs for this model. For image-capable chat models this includes values such as `"image/png"`. Empty for text-only models.', + ) name: str = Field( ..., description='Human-readable display label for this model, e.g. `"Claude Sonnet 4.6"` or `"Gemini 3.5 Flash (thinking)"`. Render this value directly in pickers rather than attempting to parse or transform `id`. Falls back to the `id` string when the catalog entry does not declare an explicit name.', ) + output_media_formats: list[str] = Field( + ..., + description="MIME types this model can emit as media in chat responses. Empty for text-output models, including image-understanding models that only return text.", + ) class ChatModelsResponse(BaseModel): @@ -184,6 +287,10 @@ class ChatModelsResponse(BaseModel): class ImageModelsResponseDataItem(BaseModel): + capabilities: list[Literal["image", "search", "thinking"]] = Field( + ..., + description='Machine-readable model capabilities. `"image"` marks image-input chat, `"search"` marks built-in web search, and `"thinking"` marks models with configurable reasoning. Empty when the catalog entry declares no special capabilities.', + ) default: bool = Field( ..., description="`true` for the model the platform selects when an agent has no `default_model` configured, or for the system-wide fallback in image-generation contexts. Exactly one entry in any given model list carries this flag.", @@ -192,10 +299,18 @@ class ImageModelsResponseDataItem(BaseModel): ..., description='Provider-assigned model identifier used when specifying a model on API requests, e.g. `"claude-sonnet-4-6"` or `"gemini-2.5-flash"`.', ) + input_media_formats: list[str] = Field( + ..., + description='MIME types accepted in chat `content_parts` image/file inputs for this model. For image-capable chat models this includes values such as `"image/png"`. Empty for text-only models.', + ) name: str = Field( ..., description='Human-readable display label for this model, e.g. `"Claude Sonnet 4.6"` or `"Gemini 3.5 Flash (thinking)"`. Render this value directly in pickers rather than attempting to parse or transform `id`. Falls back to the `id` string when the catalog entry does not declare an explicit name.', ) + output_media_formats: list[str] = Field( + ..., + description="MIME types this model can emit as media in chat responses. Empty for text-output models, including image-understanding models that only return text.", + ) class ImageModelsResponse(BaseModel): @@ -209,11 +324,51 @@ class ImageModelsResponse(BaseModel): ) -class AsyncChatResource: +class CompletionStreamEventDone(TypedDict): + event: Literal["done"] + data: AIChatStreamDone + + +class CompletionStreamEventError(TypedDict): + event: Literal["error"] + data: AIChatStreamError + + +class CompletionStreamEventMessageComplete(TypedDict): + event: Literal["message_complete"] + data: AIChatStreamMessageComplete + + +class CompletionStreamEventMessageDelta(TypedDict): + event: Literal["message_delta"] + data: AIChatStreamMessageDelta + + +class CompletionStreamEventToolCallDelta(TypedDict): + event: Literal["tool_call_delta"] + data: AIChatStreamToolCallDelta + + +class CompletionStreamEventToolResult(TypedDict): + event: Literal["tool_result"] + data: AIChatStreamToolResult + + +CompletionStreamEvent = ( + CompletionStreamEventDone + | CompletionStreamEventError + | CompletionStreamEventMessageComplete + | CompletionStreamEventMessageDelta + | CompletionStreamEventToolCallDelta + | CompletionStreamEventToolResult +) + + +class AsyncCompletionResource: def __init__(self, http: HttpClient): self._http = http - async def completions(self, input: ChatCompletionsInput) -> AICompletionResult: + async def create(self, input: CompletionCreateInput) -> AICompletionResult: """ Create a chat completion Sends a list of messages to the configured AI provider and returns a single @@ -242,6 +397,34 @@ async def completions(self, input: ChatCompletionsInput) -> AICompletionResult: response_type=AICompletionResult, ) + async def stream(self, input: CompletionStreamInput) -> AsyncIterator[CompletionStreamEvent]: + """ + Stream a chat completion + Streams a chat completion over Server-Sent Events. Emits `message_delta`, + `message_complete`, `tool_call_*`, `tool_result`, and a terminal `done` (or + `error`) event. Same request shape as the non-streaming completion endpoint; + the app must have the `llm_calls` entitlement. + + Args: + input: Request body. + input.context: Key-value map used to resolve template variables in message content. Omit if messages contain no templates. + input.messages: Ordered list of conversation messages to send to the model. + input.opts: Model and sampling configuration for this request. + + Returns: + Server-Sent Events stream + """ + async for event in self._http.stream_sse( + "/api/v1/ai/chat/completions/stream", method="POST", body=input + ): + yield event + + +class AsyncChatResource: + def __init__(self, http: HttpClient): + self._http = http + self.completions = AsyncCompletionResource(http) + async def models(self) -> ChatModelsResponse: """ List available AI models @@ -363,11 +546,11 @@ def __init__(self, http: HttpClient): self.image = AsyncImageResource(http) -class ChatResource: +class CompletionResource: def __init__(self, http: SyncHttpClient): self._http = http - def completions(self, input: ChatCompletionsInput) -> AICompletionResult: + def create(self, input: CompletionCreateInput) -> AICompletionResult: """ Create a chat completion Sends a list of messages to the configured AI provider and returns a single @@ -396,6 +579,33 @@ def completions(self, input: ChatCompletionsInput) -> AICompletionResult: response_type=AICompletionResult, ) + def stream(self, input: CompletionStreamInput) -> Iterator[CompletionStreamEvent]: + """ + Stream a chat completion + Streams a chat completion over Server-Sent Events. Emits `message_delta`, + `message_complete`, `tool_call_*`, `tool_result`, and a terminal `done` (or + `error`) event. Same request shape as the non-streaming completion endpoint; + the app must have the `llm_calls` entitlement. + + Args: + input: Request body. + input.context: Key-value map used to resolve template variables in message content. Omit if messages contain no templates. + input.messages: Ordered list of conversation messages to send to the model. + input.opts: Model and sampling configuration for this request. + + Returns: + Server-Sent Events stream + """ + yield from self._http.stream_sse_sync( + "/api/v1/ai/chat/completions/stream", method="POST", body=input + ) + + +class ChatResource: + def __init__(self, http: SyncHttpClient): + self._http = http + self.completions = CompletionResource(http) + def models(self) -> ChatModelsResponse: """ List available AI models diff --git a/src/archastro/platform/v1/resources/config.py b/src/archastro/platform/v1/resources/config.py index d8719e1..05b079d 100644 --- a/src/archastro/platform/v1/resources/config.py +++ b/src/archastro/platform/v1/resources/config.py @@ -1,6 +1,6 @@ # Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. # This file is auto-generated by @archastro/sdk-generator. Do not edit. -# Content hash: d79b9b370399 +# Content hash: 662118a1a76a from __future__ import annotations @@ -39,6 +39,8 @@ class ConfigCreateInput(TypedDict, total=False): "Human-readable description of this initial version, stored on the version record." data: dict[str, Any] | None "Arbitrary key-value metadata stored on the version alongside the content." + data_encoding: str | None + 'Encoding of `raw_content`. Omit or set `"raw"` for literal content; set `"base64"` when sending binary content such as images or PDFs in JSON.' kind: Required[str] 'Config kind that determines the schema and behavior of the config, e.g. `"Agent"` or `"APITool"`.' lookup_key: str | None @@ -81,6 +83,10 @@ class ConfigValidateInput(TypedDict, total=False): agent: str | None "Agent ID (`agt_...`) that would own the config. Used for owner-aware validation rules. Mutually exclusive with `team` and `user`." + data: dict[str, Any] | None + "Optional metadata used by kind-specific validation. File and Image configs require `data.name` when validating direct binary content." + data_encoding: str | None + 'Encoding of `raw_content`. Omit or set `"raw"` for literal content; set `"base64"` when sending binary content such as images or PDFs in JSON.' kind: Required[str] 'Config kind whose schema the content is validated against, e.g. `"Agent"` or `"APITool"`.' mime_type: Required[str] @@ -102,6 +108,8 @@ class ConfigUpdateInput(TypedDict, total=False): "Human-readable description of this update, stored on the new version record." data: dict[str, Any] | None "Arbitrary key-value metadata to store on the new version alongside the content." + data_encoding: str | None + 'Encoding of `raw_content`. Omit or set `"raw"` for literal content; set `"base64"` when sending binary content such as images or PDFs in JSON.' expected_version: int | None "Version number the caller expects to be current. If the config's actual current version does not match, the request returns 409 to signal a concurrent modification. Omit to skip optimistic locking." lookup_key: str | None @@ -743,6 +751,7 @@ async def create(self, input: ConfigCreateInput) -> Config: input.agent: Agent ID (`agt_...`) to assign as the config owner. Mutually exclusive with `team`, `user`, and `system`. input.change_description: Human-readable description of this initial version, stored on the version record. input.data: Arbitrary key-value metadata stored on the version alongside the content. + input.data_encoding: Encoding of `raw_content`. Omit or set `"raw"` for literal content; set `"base64"` when sending binary content such as images or PDFs in JSON. input.kind: Config kind that determines the schema and behavior of the config, e.g. `"Agent"` or `"APITool"`. input.lookup_key: Optional stable key for looking up this config independent of its `virtual_path`. Must be unique within the app + org scope across all owners. input.mime_type: MIME type of `raw_content`, e.g. `"application/x-yaml"` or `"application/json"`. @@ -854,6 +863,8 @@ async def validate(self, input: ConfigValidateInput) -> ValidationResult: Args: input: Request body. input.agent: Agent ID (`agt_...`) that would own the config. Used for owner-aware validation rules. Mutually exclusive with `team` and `user`. + input.data: Optional metadata used by kind-specific validation. File and Image configs require `data.name` when validating direct binary content. + input.data_encoding: Encoding of `raw_content`. Omit or set `"raw"` for literal content; set `"base64"` when sending binary content such as images or PDFs in JSON. input.kind: Config kind whose schema the content is validated against, e.g. `"Agent"` or `"APITool"`. input.mime_type: MIME type of `raw_content`, e.g. `"application/x-yaml"` or `"application/json"`. Used to parse the content before validation. input.raw_content: Raw content bytes to validate. Parsed according to `mime_type` before schema validation. @@ -963,6 +974,7 @@ async def update(self, config: str, input: ConfigUpdateInput) -> Config: input.agent: Agent ID (`agt_...`) to use as the owner when resolving by `virtual_path` or `lookup_key`. input.change_description: Human-readable description of this update, stored on the new version record. input.data: Arbitrary key-value metadata to store on the new version alongside the content. + input.data_encoding: Encoding of `raw_content`. Omit or set `"raw"` for literal content; set `"base64"` when sending binary content such as images or PDFs in JSON. input.expected_version: Version number the caller expects to be current. If the config's actual current version does not match, the request returns 409 to signal a concurrent modification. Omit to skip optimistic locking. input.lookup_key: New `lookup_key` for the config. Updates the key without creating a new version when `raw_content` is omitted. input.mime_type: MIME type of `raw_content`, e.g. `"application/x-yaml"` or `"application/json"`. Defaults to the existing MIME type when `raw_content` is provided without this field. @@ -1442,6 +1454,7 @@ def create(self, input: ConfigCreateInput) -> Config: input.agent: Agent ID (`agt_...`) to assign as the config owner. Mutually exclusive with `team`, `user`, and `system`. input.change_description: Human-readable description of this initial version, stored on the version record. input.data: Arbitrary key-value metadata stored on the version alongside the content. + input.data_encoding: Encoding of `raw_content`. Omit or set `"raw"` for literal content; set `"base64"` when sending binary content such as images or PDFs in JSON. input.kind: Config kind that determines the schema and behavior of the config, e.g. `"Agent"` or `"APITool"`. input.lookup_key: Optional stable key for looking up this config independent of its `virtual_path`. Must be unique within the app + org scope across all owners. input.mime_type: MIME type of `raw_content`, e.g. `"application/x-yaml"` or `"application/json"`. @@ -1549,6 +1562,8 @@ def validate(self, input: ConfigValidateInput) -> ValidationResult: Args: input: Request body. input.agent: Agent ID (`agt_...`) that would own the config. Used for owner-aware validation rules. Mutually exclusive with `team` and `user`. + input.data: Optional metadata used by kind-specific validation. File and Image configs require `data.name` when validating direct binary content. + input.data_encoding: Encoding of `raw_content`. Omit or set `"raw"` for literal content; set `"base64"` when sending binary content such as images or PDFs in JSON. input.kind: Config kind whose schema the content is validated against, e.g. `"Agent"` or `"APITool"`. input.mime_type: MIME type of `raw_content`, e.g. `"application/x-yaml"` or `"application/json"`. Used to parse the content before validation. input.raw_content: Raw content bytes to validate. Parsed according to `mime_type` before schema validation. @@ -1654,6 +1669,7 @@ def update(self, config: str, input: ConfigUpdateInput) -> Config: input.agent: Agent ID (`agt_...`) to use as the owner when resolving by `virtual_path` or `lookup_key`. input.change_description: Human-readable description of this update, stored on the new version record. input.data: Arbitrary key-value metadata to store on the new version alongside the content. + input.data_encoding: Encoding of `raw_content`. Omit or set `"raw"` for literal content; set `"base64"` when sending binary content such as images or PDFs in JSON. input.expected_version: Version number the caller expects to be current. If the config's actual current version does not match, the request returns 409 to signal a concurrent modification. Omit to skip optimistic locking. input.lookup_key: New `lookup_key` for the config. Updates the key without creating a new version when `raw_content` is omitted. input.mime_type: MIME type of `raw_content`, e.g. `"application/x-yaml"` or `"application/json"`. Defaults to the existing MIME type when `raw_content` is provided without this field. diff --git a/src/archastro/platform/v1/resources/solutions.py b/src/archastro/platform/v1/resources/solutions.py index 1af6cc9..aa911d9 100644 --- a/src/archastro/platform/v1/resources/solutions.py +++ b/src/archastro/platform/v1/resources/solutions.py @@ -1,6 +1,6 @@ # Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. # This file is auto-generated by @archastro/sdk-generator. Do not edit. -# Content hash: eea9dea9d3a7 +# Content hash: b18497f24bab from __future__ import annotations @@ -306,11 +306,13 @@ async def list( ) -> SolutionListResponse: """ List Solutions - Returns a paginated list of Solutions visible to the authenticated caller, - merging two scopes: app-level Solutions (system-owned rows with no org - affiliation, visible to all authenticated viewers in the app) and org-level - Solutions (system-owned rows stamped with the viewer's org ID, included when - the viewer carries an org context). + Returns a paginated list of Solutions visible to the caller, merging two + scopes: app-level Solutions (system-owned rows with no org affiliation, + visible to everyone including unauthenticated callers so they can power + the public catalog) and org-level Solutions (system-owned rows stamped with + the viewer's org ID, included when an authenticated viewer carries an org + context). Unauthenticated callers resolve to an app-scoped anonymous viewer + and therefore only ever see the app-level scope. Solutions that appear under both scopes are deduplicated by their stable `solution_id` value. The merged entry's `owners` array lists every scope the Solution was found under (`"system"` and/or `"org"`). When the app-level copy @@ -409,8 +411,10 @@ async def get(self, solution: str) -> SolutionSummary: always includes a freshly-minted `readme_url` call this endpoint to refresh an expired README token without making any other state change. Visibility matches the list endpoint: app-level Solutions (no org affiliation) - are visible to every authenticated viewer in the app; org-scoped Solutions are - only visible to viewers whose org context matches. Anything else returns 404. + are visible to everyone including unauthenticated callers, so the public + catalog can render a Solution's detail page logged-out; org-scoped Solutions + are only visible to authenticated viewers whose org context matches. Anything + else returns 404. When the resolved Solution is org-scoped, the endpoint compares its `solution_version` against the matching app-level copy. If the app-level copy is at a higher version the response includes `upgrade_available: true` and @@ -572,11 +576,13 @@ def list( ) -> SolutionListResponse: """ List Solutions - Returns a paginated list of Solutions visible to the authenticated caller, - merging two scopes: app-level Solutions (system-owned rows with no org - affiliation, visible to all authenticated viewers in the app) and org-level - Solutions (system-owned rows stamped with the viewer's org ID, included when - the viewer carries an org context). + Returns a paginated list of Solutions visible to the caller, merging two + scopes: app-level Solutions (system-owned rows with no org affiliation, + visible to everyone including unauthenticated callers so they can power + the public catalog) and org-level Solutions (system-owned rows stamped with + the viewer's org ID, included when an authenticated viewer carries an org + context). Unauthenticated callers resolve to an app-scoped anonymous viewer + and therefore only ever see the app-level scope. Solutions that appear under both scopes are deduplicated by their stable `solution_id` value. The merged entry's `owners` array lists every scope the Solution was found under (`"system"` and/or `"org"`). When the app-level copy @@ -675,8 +681,10 @@ def get(self, solution: str) -> SolutionSummary: always includes a freshly-minted `readme_url` call this endpoint to refresh an expired README token without making any other state change. Visibility matches the list endpoint: app-level Solutions (no org affiliation) - are visible to every authenticated viewer in the app; org-scoped Solutions are - only visible to viewers whose org context matches. Anything else returns 404. + are visible to everyone including unauthenticated callers, so the public + catalog can render a Solution's detail page logged-out; org-scoped Solutions + are only visible to authenticated viewers whose org context matches. Anything + else returns 404. When the resolved Solution is org-scoped, the endpoint compares its `solution_version` against the matching app-level copy. If the app-level copy is at a higher version the response includes `upgrade_available: true` and diff --git a/src/archastro/platform/v1/resources/teams.py b/src/archastro/platform/v1/resources/teams.py index ac8fad9..1e52913 100644 --- a/src/archastro/platform/v1/resources/teams.py +++ b/src/archastro/platform/v1/resources/teams.py @@ -1,11 +1,11 @@ # Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. # This file is auto-generated by @archastro/sdk-generator. Do not edit. -# Content hash: e5e662d1ebd8 +# Content hash: 7b2d4a7b95b5 from __future__ import annotations from datetime import datetime -from typing import Any, Required, TypedDict +from typing import Any, Literal, Required, TypedDict from pydantic import BaseModel, Field @@ -596,6 +596,10 @@ class MemberListResponseDataItemAgent(BaseModel): default=None, description="ID of the team that owns this agent (`tem_...`). `null` if the agent is not team-scoped.", ) + template_upgrade_available: bool | None = Field( + default=None, + description="True when the agent's last-applied template version is behind the current version of its AgentTemplate config i.e. reapplying the template (a per-agent upgrade) would bring it newer Solution content. Self-clears once the agent is reapplied. Computed only on list endpoints; `null` on single-agent GET.", + ) updated_at: datetime | None = Field( default=None, description="When the agent was last modified (ISO 8601)." ) @@ -1034,6 +1038,10 @@ class TeamThreadListResponseDataItemParentMessage(BaseModel): default=None, description="ID of the agent user that sent this message (`agi_...`). `null` for messages sent by human users.", ) + agent_mode: Literal["cli", "embedded"] | None = Field( + default=None, + description="Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path.", + ) attachments: list[TeamThreadListResponseDataItemParentMessageAttachmentsItem] | None = Field( default=None, description="Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.", @@ -1493,6 +1501,10 @@ class TeamThreadListResponseDataItemParticipatingAgentsItem(BaseModel): default=None, description="ID of the team that owns this agent (`tem_...`). `null` if the agent is not team-scoped.", ) + template_upgrade_available: bool | None = Field( + default=None, + description="True when the agent's last-applied template version is behind the current version of its AgentTemplate config i.e. reapplying the template (a per-agent upgrade) would bring it newer Solution content. Self-clears once the agent is reapplied. Computed only on list endpoints; `null` on single-agent GET.", + ) updated_at: datetime | None = Field( default=None, description="When the agent was last modified (ISO 8601)." ) diff --git a/src/archastro/platform/v1/resources/threads.py b/src/archastro/platform/v1/resources/threads.py index 9c8463c..9c9db56 100644 --- a/src/archastro/platform/v1/resources/threads.py +++ b/src/archastro/platform/v1/resources/threads.py @@ -1,11 +1,11 @@ # Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. # This file is auto-generated by @archastro/sdk-generator. Do not edit. -# Content hash: 5666f00cc081 +# Content hash: d3e40763df8d from __future__ import annotations from datetime import datetime -from typing import Any, Required, TypedDict +from typing import Any, Literal, Required, TypedDict from pydantic import BaseModel, Field @@ -532,6 +532,10 @@ class ThreadMessagesResponseDataMessagesItem(BaseModel): default=None, description="ID of the agent user that sent this message (`agi_...`). `null` for messages sent by human users.", ) + agent_mode: Literal["cli", "embedded"] | None = Field( + default=None, + description="Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path.", + ) attachments: list[ThreadMessagesResponseDataMessagesItemAttachmentsItem] | None = Field( default=None, description="Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.", @@ -618,6 +622,10 @@ class ThreadMessagesResponseData(BaseModel): default=None, description="Opaque cursor to pass as `after` to retrieve the page of messages newer than this result set. `null` when there are no later messages.", ) + anchor: str | None = Field( + default=None, + description="Message ID used as the anchor for a windowed query. `null` for ordinary cursor pagination.", + ) before_cursor: str | None = Field( default=None, description="Opaque cursor to pass as `before` to retrieve the page of messages older than this result set. `null` when there are no earlier messages.", @@ -911,13 +919,24 @@ async def messages( before_cursor: str | None = None, after_cursor: str | None = None, limit: int | None = None, + anchor: str | None = None, + direction: Literal["before", "after", "around"] | None = None, + before_limit: int | None = None, + after_limit: int | None = None, + include_anchor: bool | None = None, + anchor_agent_mode: Literal["cli", "embedded"] | None = None, + anchor_agent: str | None = None, include_reply_counts: bool | None = None, ) -> ThreadMessagesResponse: """ List messages in a thread Returns a cursor-paginated list of messages belonging to the specified thread, - ordered from oldest to newest. Supply `before_cursor` or `after_cursor` (not both) - to page through the result set; omit both to receive the most recent page. + ordered from oldest to newest. Supply `before_cursor`, `after_cursor`, or both + to page through or bound the result set; omit both to receive the most recent page. + Supply `anchor` and `direction` to fetch a window before, after, or around a + specific message. Use `anchor=last_matching&anchor_agent_mode=embedded` to + resolve the anchor from the latest embedded-agent message, and add + `anchor_agent` to scope that resolution to a single sender agent. The authenticated user must have access to the thread's owner (workspace or user). A 403 is returned if the thread exists but is not accessible to the caller; a 404 is returned if the thread does not exist or is not visible to the authenticated user. @@ -927,9 +946,16 @@ async def messages( Args: thread: Thread ID (`thr_...`). The authenticated user must have access to this thread. - before_cursor: Opaque cursor returned in a previous response's `before_cursor` field. When provided, returns the page of messages immediately before that position. Cannot be combined with `after_cursor`. - after_cursor: Opaque cursor returned in a previous response's `after_cursor` field. When provided, returns the page of messages immediately after that position. Cannot be combined with `before_cursor`. + before_cursor: Opaque cursor returned in a previous response's `before_cursor` field. When provided, returns messages immediately before that position. May be combined with `after_cursor` to bound a range. + after_cursor: Opaque cursor returned in a previous response's `after_cursor` field. When provided, returns messages immediately after that position. May be combined with `before_cursor` to bound a range. limit: Maximum number of messages to return per page. Defaults to 20. + anchor: Message ID (`msg_...`) to use as a window anchor, or `last_matching` to resolve the anchor from the latest message matching the anchor filters. Cannot be combined with `before_cursor` or `after_cursor`. + direction: Window direction relative to `anchor`. `before` returns older messages, `after` returns newer messages, and `around` returns messages on both sides. Defaults to `after` when `anchor` is supplied. `direction=around` cannot be combined with an explicit `limit`; use `before_limit` and `after_limit`. + before_limit: For `direction=around`, maximum number of messages older than the anchor. Defaults to 20. + after_limit: For `direction=around`, maximum number of messages newer than the anchor. Defaults to 20. + include_anchor: Whether to include the anchor message in a window response. Defaults to `true` for `direction=around`; ignored for ordinary cursor pagination and one-sided windows. + anchor_agent_mode: When `anchor=last_matching`, resolve the anchor from the latest message with this local agent execution mode. + anchor_agent: When `anchor=last_matching`, scope the anchor resolution to messages sent by this agent (`agi_...`). Combine with `anchor_agent_mode` to resolve the latest message from a specific agent in a given mode. include_reply_counts: When `true`, each message in the response is annotated with its threaded reply count. Defaults to `false`. Adds latency; omit when reply counts are not needed. Returns: @@ -942,6 +968,20 @@ async def messages( query["after_cursor"] = after_cursor if limit is not None: query["limit"] = limit + if anchor is not None: + query["anchor"] = anchor + if direction is not None: + query["direction"] = direction + if before_limit is not None: + query["before_limit"] = before_limit + if after_limit is not None: + query["after_limit"] = after_limit + if include_anchor is not None: + query["include_anchor"] = include_anchor + if anchor_agent_mode is not None: + query["anchor_agent_mode"] = anchor_agent_mode + if anchor_agent is not None: + query["anchor_agent"] = anchor_agent if include_reply_counts is not None: query["include_reply_counts"] = include_reply_counts return await self._http.request( @@ -1292,13 +1332,24 @@ def messages( before_cursor: str | None = None, after_cursor: str | None = None, limit: int | None = None, + anchor: str | None = None, + direction: Literal["before", "after", "around"] | None = None, + before_limit: int | None = None, + after_limit: int | None = None, + include_anchor: bool | None = None, + anchor_agent_mode: Literal["cli", "embedded"] | None = None, + anchor_agent: str | None = None, include_reply_counts: bool | None = None, ) -> ThreadMessagesResponse: """ List messages in a thread Returns a cursor-paginated list of messages belonging to the specified thread, - ordered from oldest to newest. Supply `before_cursor` or `after_cursor` (not both) - to page through the result set; omit both to receive the most recent page. + ordered from oldest to newest. Supply `before_cursor`, `after_cursor`, or both + to page through or bound the result set; omit both to receive the most recent page. + Supply `anchor` and `direction` to fetch a window before, after, or around a + specific message. Use `anchor=last_matching&anchor_agent_mode=embedded` to + resolve the anchor from the latest embedded-agent message, and add + `anchor_agent` to scope that resolution to a single sender agent. The authenticated user must have access to the thread's owner (workspace or user). A 403 is returned if the thread exists but is not accessible to the caller; a 404 is returned if the thread does not exist or is not visible to the authenticated user. @@ -1308,9 +1359,16 @@ def messages( Args: thread: Thread ID (`thr_...`). The authenticated user must have access to this thread. - before_cursor: Opaque cursor returned in a previous response's `before_cursor` field. When provided, returns the page of messages immediately before that position. Cannot be combined with `after_cursor`. - after_cursor: Opaque cursor returned in a previous response's `after_cursor` field. When provided, returns the page of messages immediately after that position. Cannot be combined with `before_cursor`. + before_cursor: Opaque cursor returned in a previous response's `before_cursor` field. When provided, returns messages immediately before that position. May be combined with `after_cursor` to bound a range. + after_cursor: Opaque cursor returned in a previous response's `after_cursor` field. When provided, returns messages immediately after that position. May be combined with `before_cursor` to bound a range. limit: Maximum number of messages to return per page. Defaults to 20. + anchor: Message ID (`msg_...`) to use as a window anchor, or `last_matching` to resolve the anchor from the latest message matching the anchor filters. Cannot be combined with `before_cursor` or `after_cursor`. + direction: Window direction relative to `anchor`. `before` returns older messages, `after` returns newer messages, and `around` returns messages on both sides. Defaults to `after` when `anchor` is supplied. `direction=around` cannot be combined with an explicit `limit`; use `before_limit` and `after_limit`. + before_limit: For `direction=around`, maximum number of messages older than the anchor. Defaults to 20. + after_limit: For `direction=around`, maximum number of messages newer than the anchor. Defaults to 20. + include_anchor: Whether to include the anchor message in a window response. Defaults to `true` for `direction=around`; ignored for ordinary cursor pagination and one-sided windows. + anchor_agent_mode: When `anchor=last_matching`, resolve the anchor from the latest message with this local agent execution mode. + anchor_agent: When `anchor=last_matching`, scope the anchor resolution to messages sent by this agent (`agi_...`). Combine with `anchor_agent_mode` to resolve the latest message from a specific agent in a given mode. include_reply_counts: When `true`, each message in the response is annotated with its threaded reply count. Defaults to `false`. Adds latency; omit when reply counts are not needed. Returns: @@ -1323,6 +1381,20 @@ def messages( query["after_cursor"] = after_cursor if limit is not None: query["limit"] = limit + if anchor is not None: + query["anchor"] = anchor + if direction is not None: + query["direction"] = direction + if before_limit is not None: + query["before_limit"] = before_limit + if after_limit is not None: + query["after_limit"] = after_limit + if include_anchor is not None: + query["include_anchor"] = include_anchor + if anchor_agent_mode is not None: + query["anchor_agent_mode"] = anchor_agent_mode + if anchor_agent is not None: + query["anchor_agent"] = anchor_agent if include_reply_counts is not None: query["include_reply_counts"] = include_reply_counts return self._http.request( diff --git a/src/archastro/platform/v1/resources/users.py b/src/archastro/platform/v1/resources/users.py index 58bda7e..bdcee16 100644 --- a/src/archastro/platform/v1/resources/users.py +++ b/src/archastro/platform/v1/resources/users.py @@ -1,12 +1,12 @@ # Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. # This file is auto-generated by @archastro/sdk-generator. Do not edit. -# Content hash: a3c7d5165ca2 +# Content hash: 98807ba6a0d8 from __future__ import annotations import builtins from datetime import datetime -from typing import Any, Required, TypedDict +from typing import Any, Literal, Required, TypedDict from pydantic import BaseModel, Field @@ -396,6 +396,10 @@ class UserThreadListResponseDataItemParentMessage(BaseModel): default=None, description="ID of the agent user that sent this message (`agi_...`). `null` for messages sent by human users.", ) + agent_mode: Literal["cli", "embedded"] | None = Field( + default=None, + description="Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path.", + ) attachments: list[UserThreadListResponseDataItemParentMessageAttachmentsItem] | None = Field( default=None, description="Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.", @@ -855,6 +859,10 @@ class UserThreadListResponseDataItemParticipatingAgentsItem(BaseModel): default=None, description="ID of the team that owns this agent (`tem_...`). `null` if the agent is not team-scoped.", ) + template_upgrade_available: bool | None = Field( + default=None, + description="True when the agent's last-applied template version is behind the current version of its AgentTemplate config i.e. reapplying the template (a per-agent upgrade) would bring it newer Solution content. Self-clears once the agent is reapplied. Computed only on list endpoints; `null` on single-agent GET.", + ) updated_at: datetime | None = Field( default=None, description="When the agent was last modified (ISO 8601)." ) diff --git a/tests/contract/channels/test_api_chat_channel.py b/tests/contract/channels/test_api_chat_channel.py index 64c1db9..4e8bce8 100644 --- a/tests/contract/channels/test_api_chat_channel.py +++ b/tests/contract/channels/test_api_chat_channel.py @@ -1,6 +1,6 @@ # Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. # This file is auto-generated by @archastro/sdk-generator. Do not edit. -# Content hash: 569a22633273 +# Content hash: 828a7723e605 """ Contract tests for ApiChatChannel — generated from the channel spec. @@ -750,6 +750,57 @@ async def test_api_chat_channel_api_chat_remove_reaction_returns_error_envelope_ assert reply["status"] == "error" +async def test_api_chat_channel_api_chat_typing_sends_valid_push_and_receives_contract_valid_reply( + rig, +): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + "onMessage": { + "api:chat:typing": [{"type": "autoReply"}], + }, + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + reply = await channel.api_chat_typing({"is_typing": True}) + assert reply["status"] == "ok" + + observed = await client.observations("api:chat:team:test-id:thread:test-id", "api:chat:typing") + assert len(observed) == 1 + assert observed[0]["params"]["is_typing"] is True + + +async def test_api_chat_channel_api_chat_typing_returns_error_envelope_when_required_missing(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [{"type": "autoReply"}], + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + reply = await channel.api_chat_typing({}) + assert reply["status"] == "error" + + async def test_api_chat_channel_on_message_added_delivers_contract_valid_payloads(rig): client, socket = rig await client.register_scenario( @@ -874,6 +925,37 @@ def handler(payload): assert payload is not None +async def test_api_chat_channel_on_typing_delivers_contract_valid_payloads(rig): + client, socket = rig + await client.register_scenario( + { + "topic": "api:chat:team:test-id:thread:test-id", + "onJoin": [ + {"type": "autoReply"}, + {"type": "autoPush", "event": "typing"}, + ], + } + ) + channel = await ApiChatChannel.join_team_thread( + socket, + "test-id", + "test-id", + after_cursor="test-value", + before_cursor="test-value", + include_metadata=True, + limit=1, + ) + future: asyncio.Future = asyncio.get_event_loop().create_future() + + def handler(payload): + if not future.done(): + future.set_result(payload) + + channel.on_typing(handler) + payload = await asyncio.wait_for(future, timeout=1.0) + assert payload is not None + + async def test_api_chat_channel_leave_leaves_cleanly_through_generated_leave(rig): client, socket = rig await client.register_scenario( diff --git a/tests/contract/conftest.py b/tests/contract/conftest.py index 1c9bcd3..c74df34 100644 --- a/tests/contract/conftest.py +++ b/tests/contract/conftest.py @@ -1,6 +1,6 @@ # Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. # This file is auto-generated by @archastro/sdk-generator. Do not edit. -# Content hash: 7776fe5e8cd1 +# Content hash: e7c368998eea import json import os @@ -44,7 +44,7 @@ def _channel_tests_enabled() -> bool: # Skip the channel test tree entirely at collection time when the env var # is not set, so CI runs REST tests without pulling in the harness service. -collect_ignore_glob = [] if _channel_tests_enabled() else ["channels/*"] +collect_ignore_glob = [] if _channel_tests_enabled() else ["channels/*", "streams/*"] def pytest_configure(config): diff --git a/tests/contract/streams/test_ai.py b/tests/contract/streams/test_ai.py new file mode 100644 index 0000000..2ffbe70 --- /dev/null +++ b/tests/contract/streams/test_ai.py @@ -0,0 +1,84 @@ +# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. +# This file is auto-generated by @archastro/sdk-generator. Do not edit. +# Content hash: 04f6f272f827 + +import pytest + +from archastro.phx_channel import HarnessServiceClient +from archastro.platform import AsyncPlatformClient +from archastro.platform.runtime.http_client import ApiError + +# Mark every coroutine in this file as async so the tests run whether or +# not the consuming project sets asyncio_mode=auto. +pytestmark = pytest.mark.asyncio + + +async def test_ai_chat_completions_stream_yields_sse_events(harness_service): + harness = HarnessServiceClient( + ws_url=harness_service["wsUrl"], control_url=harness_service["controlUrl"] + ) + await harness.reset() + await harness.register_stream_scenario( + { + "route": "POST /api/v1/ai/chat/completions/stream", + "actions": [ + {"type": "autoEmit", "event": "done"}, + {"type": "autoEmit", "event": "error"}, + {"type": "autoEmit", "event": "message_complete"}, + {"type": "autoEmit", "event": "message_delta"}, + {"type": "autoEmit", "event": "tool_call_delta"}, + {"type": "autoEmit", "event": "tool_result"}, + ], + } + ) + client = AsyncPlatformClient( + base_url=harness_service["controlUrl"], + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + try: + events = [] + async for ev in client.v1.ai.chat.completions.stream( + {"messages": [{"role": "user"}], "opts": {"model": "test-model"}} + ): + events.append(ev) + assert [e["event"] for e in events] == [ + "done", + "error", + "message_complete", + "message_delta", + "tool_call_delta", + "tool_result", + ] + finally: + await client.close() + await harness.close() + + +async def test_ai_chat_completions_stream_rejects_non_2xx(harness_service): + harness = HarnessServiceClient( + ws_url=harness_service["wsUrl"], control_url=harness_service["controlUrl"] + ) + await harness.reset() + await harness.register_stream_scenario( + { + "route": "POST /api/v1/ai/chat/completions/stream", + "actions": [ + {"type": "status", "code": 402, "body": {"error": {"code": "plan_not_entitled"}}} + ], + } + ) + client = AsyncPlatformClient( + base_url=harness_service["controlUrl"], + default_headers={"x-archastro-api-key": "pk_test-key"}, + access_token="test-token", + ) + try: + with pytest.raises(ApiError): + async for _ in client.v1.ai.chat.completions.stream( + {"messages": [{"role": "user"}], "opts": {"model": "test-model"}} + ): + pass + finally: + await client.close() + await harness.close() diff --git a/tests/contract/v1/test_ai.py b/tests/contract/v1/test_ai.py index 4b0bba3..8b633a1 100644 --- a/tests/contract/v1/test_ai.py +++ b/tests/contract/v1/test_ai.py @@ -1,6 +1,6 @@ # Copyright (c) 2026 ArchAstro Inc. All Rights Reserved. # This file is auto-generated by @archastro/sdk-generator. Do not edit. -# Content hash: 2ce841525286 +# Content hash: 074b610d41b5 import pytest from pydantic import BaseModel @@ -43,10 +43,54 @@ def _async_error_client(code: int) -> AsyncPlatformClient: ) -def test_ai_chat_completions_success(): +def test_ai_chat_models_success(): + client = _client() + try: + result = client.v1.ai.chat.models() + assert isinstance(result, BaseModel) + assert type(result).__name__ == "ChatModelsResponse" + assert isinstance(result.data, list) + finally: + client.close() + + +def test_ai_chat_models_error_401(): + ec = _error_client(401) + try: + with pytest.raises(ApiError) as exc_info: + ec.v1.ai.chat.models() + assert exc_info.value.status == 401 + finally: + ec.close() + + +@pytest.mark.asyncio +async def test_async_ai_chat_models_success(): + client = _async_client() + try: + result = await client.v1.ai.chat.models() + assert isinstance(result, BaseModel) + assert type(result).__name__ == "ChatModelsResponse" + assert isinstance(result.data, list) + finally: + await client.close() + + +@pytest.mark.asyncio +async def test_async_ai_chat_models_error_401(): + ec = _async_error_client(401) + try: + with pytest.raises(ApiError) as exc_info: + await ec.v1.ai.chat.models() + assert exc_info.value.status == 401 + finally: + await ec.close() + + +def test_ai_chat_completions_create_success(): client = _client() try: - result = client.v1.ai.chat.completions( + result = client.v1.ai.chat.completions.create( {"messages": [{"role": "user"}], "opts": {"model": "test-model"}} ) assert isinstance(result, BaseModel) @@ -55,11 +99,11 @@ def test_ai_chat_completions_success(): client.close() -def test_ai_chat_completions_error_400(): +def test_ai_chat_completions_create_error_400(): ec = _error_client(400) try: with pytest.raises(ApiError) as exc_info: - ec.v1.ai.chat.completions( + ec.v1.ai.chat.completions.create( {"messages": [{"role": "user"}], "opts": {"model": "test-model"}} ) assert exc_info.value.status == 400 @@ -67,11 +111,11 @@ def test_ai_chat_completions_error_400(): ec.close() -def test_ai_chat_completions_error_401(): +def test_ai_chat_completions_create_error_401(): ec = _error_client(401) try: with pytest.raises(ApiError) as exc_info: - ec.v1.ai.chat.completions( + ec.v1.ai.chat.completions.create( {"messages": [{"role": "user"}], "opts": {"model": "test-model"}} ) assert exc_info.value.status == 401 @@ -79,11 +123,11 @@ def test_ai_chat_completions_error_401(): ec.close() -def test_ai_chat_completions_error_402(): +def test_ai_chat_completions_create_error_402(): ec = _error_client(402) try: with pytest.raises(ApiError) as exc_info: - ec.v1.ai.chat.completions( + ec.v1.ai.chat.completions.create( {"messages": [{"role": "user"}], "opts": {"model": "test-model"}} ) assert exc_info.value.status == 402 @@ -91,11 +135,11 @@ def test_ai_chat_completions_error_402(): ec.close() -def test_ai_chat_completions_error_422(): +def test_ai_chat_completions_create_error_422(): ec = _error_client(422) try: with pytest.raises(ApiError) as exc_info: - ec.v1.ai.chat.completions( + ec.v1.ai.chat.completions.create( {"messages": [{"role": "user"}], "opts": {"model": "test-model"}} ) assert exc_info.value.status == 422 @@ -104,10 +148,10 @@ def test_ai_chat_completions_error_422(): @pytest.mark.asyncio -async def test_async_ai_chat_completions_success(): +async def test_async_ai_chat_completions_create_success(): client = _async_client() try: - result = await client.v1.ai.chat.completions( + result = await client.v1.ai.chat.completions.create( {"messages": [{"role": "user"}], "opts": {"model": "test-model"}} ) assert isinstance(result, BaseModel) @@ -117,11 +161,11 @@ async def test_async_ai_chat_completions_success(): @pytest.mark.asyncio -async def test_async_ai_chat_completions_error_400(): +async def test_async_ai_chat_completions_create_error_400(): ec = _async_error_client(400) try: with pytest.raises(ApiError) as exc_info: - await ec.v1.ai.chat.completions( + await ec.v1.ai.chat.completions.create( {"messages": [{"role": "user"}], "opts": {"model": "test-model"}} ) assert exc_info.value.status == 400 @@ -130,11 +174,11 @@ async def test_async_ai_chat_completions_error_400(): @pytest.mark.asyncio -async def test_async_ai_chat_completions_error_401(): +async def test_async_ai_chat_completions_create_error_401(): ec = _async_error_client(401) try: with pytest.raises(ApiError) as exc_info: - await ec.v1.ai.chat.completions( + await ec.v1.ai.chat.completions.create( {"messages": [{"role": "user"}], "opts": {"model": "test-model"}} ) assert exc_info.value.status == 401 @@ -143,11 +187,11 @@ async def test_async_ai_chat_completions_error_401(): @pytest.mark.asyncio -async def test_async_ai_chat_completions_error_402(): +async def test_async_ai_chat_completions_create_error_402(): ec = _async_error_client(402) try: with pytest.raises(ApiError) as exc_info: - await ec.v1.ai.chat.completions( + await ec.v1.ai.chat.completions.create( {"messages": [{"role": "user"}], "opts": {"model": "test-model"}} ) assert exc_info.value.status == 402 @@ -156,11 +200,11 @@ async def test_async_ai_chat_completions_error_402(): @pytest.mark.asyncio -async def test_async_ai_chat_completions_error_422(): +async def test_async_ai_chat_completions_create_error_422(): ec = _async_error_client(422) try: with pytest.raises(ApiError) as exc_info: - await ec.v1.ai.chat.completions( + await ec.v1.ai.chat.completions.create( {"messages": [{"role": "user"}], "opts": {"model": "test-model"}} ) assert exc_info.value.status == 422 @@ -168,50 +212,6 @@ async def test_async_ai_chat_completions_error_422(): await ec.close() -def test_ai_chat_models_success(): - client = _client() - try: - result = client.v1.ai.chat.models() - assert isinstance(result, BaseModel) - assert type(result).__name__ == "ChatModelsResponse" - assert isinstance(result.data, list) - finally: - client.close() - - -def test_ai_chat_models_error_401(): - ec = _error_client(401) - try: - with pytest.raises(ApiError) as exc_info: - ec.v1.ai.chat.models() - assert exc_info.value.status == 401 - finally: - ec.close() - - -@pytest.mark.asyncio -async def test_async_ai_chat_models_success(): - client = _async_client() - try: - result = await client.v1.ai.chat.models() - assert isinstance(result, BaseModel) - assert type(result).__name__ == "ChatModelsResponse" - assert isinstance(result.data, list) - finally: - await client.close() - - -@pytest.mark.asyncio -async def test_async_ai_chat_models_error_401(): - ec = _async_error_client(401) - try: - with pytest.raises(ApiError) as exc_info: - await ec.v1.ai.chat.models() - assert exc_info.value.status == 401 - finally: - await ec.close() - - def test_ai_image_edits_success(): client = _client() try: