Cursor Python SDK
Пакет cursor-sdk позволяет вызывать агента Cursor из собственного кода на Python. Того же агента, который работает в Cursor IDE, CLI и веб-приложении, можно использовать в Python-скриптах с синхронными и асинхронными клиентами, типизированными датаклассами и обычной итерацией по потокам и страницам. Чтобы начать, запустите навык /sdk в Cursor.
REST API описан в разделе API облачных агентов. Для других языков см. SDK Bridge.
Обзор
SDK предоставляет единый интерфейс для локальных и облачных сред выполнения. Вы пишете один и тот же код независимо от того, где работает Agent.
| Среда выполнения | Что делает | Когда использовать |
|---|---|---|
| Локальная | Запускает Agent с локальными файлами на диске. | Скрипты для разработки и проверки CI в рабочем дереве. |
| Облачная (размещаемая Cursor) | Запускается в изолированной ВМ с клонированным репозиторием. ВМ запускает Cursor. | Когда у вызывающей стороны нет репозитория, нужно запустить много агентов параллельно или выполнение должно продолжаться после отключения вызывающей стороны. |
Задайте среду выполнения, передав local или cloud в Agent.create().
Аутентификация
Перед созданием агента задайте CURSOR_API_KEY или передайте api_key.
SDK поддерживает пользовательские API-ключи и API-ключи сервисных аккаунтов для локальных и облачных запусков. API-ключи Team Admin пока не поддерживаются.
- Пользовательский API-ключ в Cursor Dashboard -> API Keys
- API-ключ сервисного аккаунта в Team settings. См. Service accounts
export CURSOR_API_KEY="your-key"Использование и оплата
К запускам SDK применяются те же правила ценообразования, пулов запросов и режима конфиденциальности, что и к запускам из IDE и Cloud Agents. Расходы отображаются на дашборде использования вашей команды с тегом SDK.
Чтобы получать в коде количество токенов для каждого запуска, см. Использование токенов. Чтобы получить данные об оплачиваемом использовании и стоимости запусков агента в долларах, см. agent.get_usage().
Основные понятия
| Понятие | Описание |
|---|---|
| Agent | Долговечный дескриптор, хранящий состояние диалога, конфигурацию рабочего пространства, выбранную модель и настройки. Сохраняется между несколькими промптами. |
| Run | Одна отправка промпта. Имеет собственные поток, статус, результат, диалог и отмену. |
| SDKMessage | Типизированное потоковое сообщение, передаваемое во время запуска. Имеет одинаковую структуру в локальных и облачных средах выполнения. |
| CursorClient | Явный клиент для управления жизненным циклом, пользовательских параметров HTTP или работы с несколькими рабочими пространствами в одном процессе. Client — псевдоним. |
| AsyncClient | Асинхронный клиент-зеркало. Необходим для всех асинхронных операций. |
Установка
pip install cursor-sdkТребуется Python 3.10 или новее.
Быстрый старт
import osfrom cursor_sdk import Agent, LocalAgentOptionswith Agent.create( model="composer-2.5", api_key="crsr_key", local=LocalAgentOptions(cwd=os.getcwd()),) as agent: print(agent.send("Summarize what this repository does").text())События потока показывают, как извлекать текст ассистента, обрабатывать вызовы инструментов и получать состояние запуска. Для одноразового промпта (создать, запустить, завершить) см. Agent.prompt().
Быстрый старт с Cloud
Python SDK поддерживает облачных агентов Cursor. Вы можете получить список подключённых репозиториев, запустить агента для одного из них, дождаться завершения запуска и просмотреть итоговый результат.
from cursor_sdk import Agent, CloudAgentOptions, CloudRepositorywith Agent.create( model="composer-2.5", api_key="crsr_key", cloud=CloudAgentOptions( repos=[CloudRepository(url="https://github.com/your-org/your-repo", starting_ref="main")], auto_create_pr=True, ),) as agent: print(agent.send("Add structured logging to the auth middleware").text())Облачные агенты, запущенные через SDK, не отображаются в списке агентов по умолчанию. Чтобы просмотреть их в Cursor Web или окне Cursor agents, нажмите Filter > Source > SDK.
Асинхронное использование
Асинхронный клиент имеет тот же интерфейс, что и синхронный, и рекомендуется для серверов, ботов и одновременного управления несколькими Agent. AsyncAgent, AsyncClient, AsyncRun и AsyncCursor экспортируются из cursor_sdk и cursor_sdk.asyncio.
import asyncioimport osfrom cursor_sdk import AsyncClient, LocalAgentOptionsasync def main(): async with await AsyncClient.launch_bridge(workspace=os.getcwd()) as client: async with await client.agents.create( model="composer-2.5", api_key="crsr_key", local=LocalAgentOptions(cwd=os.getcwd()), ) as agent: run = await agent.send("Summarize what this repository does") print(await run.text())asyncio.run(main())Глобального асинхронного-клиента по умолчанию нет. Явно создавайте AsyncClient или используйте AsyncClient.launch_bridge(...) в качестве асинхронного context manager, чтобы каждый цикл событий использовал собственный клиент. Не смешивайте синхронные- и асинхронные-клиенты в одном пути выполнения кода.
| Sync | Async |
|---|---|
CursorClient / Client | AsyncClient / AsyncCursorClient |
Agent | AsyncAgent |
Run | AsyncRun |
Cursor | AsyncCursor |
ListResult | AsyncListResult |
DefaultHttpxClient | DefaultAsyncHttpxClient |
Создание Agent'ов
Agent.create() проверяет параметры и сразу возвращает дескриптор. Укажите local или cloud, чтобы выбрать среду выполнения.
from cursor_sdk import Agent, CloudAgentOptions, CloudRepository, LocalAgentOptionsagent = Agent.create( model="composer-2.5", local=LocalAgentOptions(cwd="."),)cloud_agent = Agent.create( model="composer-2.5", cloud=CloudAgentOptions( repos=[CloudRepository(url="https://github.com/your-org/your-repo", starting_ref="main")], auto_create_pr=True, ),)agent.agent_id заполняется сразу. Локальным агентам присваивается идентификатор agent-<uuid>, а облачным — bc-<uuid>. agent.model — типизированный объект ModelSelection, поэтому можно напрямую обращаться к agent.model.id и agent.model.params.
Облачные агенты, запущенные через SDK, не отображаются в списке агентов по умолчанию. Чтобы увидеть их в Cursor Web или окне агента Cursor, нажмите Фильтр > Источник > SDK.
Переменные среды сессии
Для облачных агентов передавайте env_vars, если запуску нужны кратковременные учетные данные или другие значения, доступные только этому агенту.
agent = Agent.create( model="composer-2.5", cloud=CloudAgentOptions( repos=[CloudRepository(url="https://github.com/your-org/your-repo")], env_vars={ "STAGING_API_TOKEN": os.environ["STAGING_API_TOKEN"], }, ),)Эти значения шифруются при хранении, передаются в оболочку облачного агента и удаляются вместе с ним. env_vars нельзя использовать с agent_id, заданным вызывающей стороной; не указывайте agent_id и считайте идентификатор, сгенерированный сервером, из agent.agent_id. Имена переменных не могут начинаться с CURSOR_.
Для значений, которые должны существовать только во время одного запуска, передавайте их в agent.send(). См. Переменные среды для одного запуска.
Метаданные Agent
При создании облачного Agent добавьте к нему собственные идентификаторы. Метаданные позволяют связать Agent с пользователем, тенантом, рабочим процессом или тикетом в вашей системе. Их можно получить из SDKAgentInfo.metadata, возвращаемого методами client.agents.get() и client.agents.list().
from cursor_sdk import Agent, CloudAgentOptions, CloudRepositorywith Agent.create( model="composer-2.5", cloud=CloudAgentOptions( repos=[CloudRepository(url="https://github.com/your-org/your-repo")], metadata={ "end_user_id": "user-123", "ticket_id": "ENG-456", }, ),) as agent: print(agent.agent_id)Метаданные можно задать для облачных Agent при создании. Можно прикрепить до 50 пар «ключ — значение». Ключи не должны быть пустыми и не должны превышать 255 символов. Значения должны быть строками размером не более 4096 байт. Допускаются пустые строковые значения, а пустое сопоставление считается отсутствием метаданных.
Если метаданные не включены для аккаунта API-ключа, при создании Agent с
непустым сопоставлением возвращается 403 feature_unavailable.
Параметры модели
Используйте ModelSelection.params для передачи параметров конкретной модели, например уровня усилий при рассуждении или optimize_for для Cursor Router. Идентификаторы и значения параметров различаются в зависимости от модели. Используйте Cursor.models.list(), чтобы узнать, какие параметры и предустановленные варианты доступны для вашего аккаунта.
from cursor_sdk import Agent, LocalAgentOptions, ModelParameterValue, ModelSelectionagent = Agent.create( model=ModelSelection( id="composer-2.5", params=[ModelParameterValue(id="fast", value="true")], ), local=LocalAgentOptions(cwd="."),)Используйте Cursor.models.list(), чтобы узнать идентификаторы параметров и предустановленные варианты для конкретной модели. О контракте выбора auto-smart см. раздел Cursor Router.
Cursor Router
Cursor Router выбирает модель для каждого запроса в режиме Auto. В SDK Router представлен моделью auto-smart с параметром optimize_for. Он доступен в Teams и Enterprise. Администраторы Enterprise должны включить Router для команды, прежде чем auto-smart появится в каталоге.
Cursor SDK — это SDK для агентов, а не самостоятельный API для инференса моделей или чат-комплишенов. Router выбирает модели для запусков агентов Cursor, которые могут работать с рабочим пространством, вызывать инструменты, выполнять команды и редактировать файлы. В настоящее время Cursor не документирует raw-эндпоинт Router для произвольных вызовов моделей.
Выберите Cost, Balance или Intelligence
Передайте auto-smart и явно задайте optimize_for:
| Метка продукта | Значение SDK |
|---|---|
| Cost | cost |
| Balance | balanced |
| Intelligence | intelligence |
В текстах продукта используйте Balance. balanced используйте только в качестве передаваемого значения SDK.
import osfrom cursor_sdk import Agent, LocalAgentOptions, ModelParameterValue, ModelSelectionwith Agent.create( model=ModelSelection( id="auto-smart", params=[ModelParameterValue(id="optimize_for", value="balanced")], ), local=LocalAgentOptions(cwd=os.getcwd()),) as agent: run = agent.send("Find and fix the failing authentication test") result = run.wait() print(result.status)Всегда передавайте optimize_for. Не опускайте этот параметр и не отправляйте устаревшее значение default; поддерживаемый способ — обнаружение через каталог.
Router в каталоге моделей
Cursor.models.list() возвращает модели, определения параметров и предустановленные варианты, доступные текущему аккаунту и команде API-ключа. Если Router доступен, Cursor Router отображается как auto-smart. Администраторы команды могут отключить Router или ограничить режимы оптимизации, доступные участникам.
Перед жёстким заданием выбора используйте каталог как источник актуальных данных:
from cursor_sdk import Cursor, ModelParameterValue, ModelSelectionmodels = Cursor.models.list()router = next((model for model in models if model.id == "auto-smart"), None)optimize_for = next( ( parameter for parameter in (router.parameters if router else []) if parameter.id == "optimize_for" ), None,)if router is None or optimize_for is None: raise RuntimeError( "Cursor Router is not available for this API key. " "Verify that Router is enabled for the key's team." )requested_mode = "balanced"allowed_values = {entry.value for entry in optimize_for.values}if requested_mode not in allowed_values: raise RuntimeError( f'Router mode "{requested_mode}" is not enabled for this team.' )model = ModelSelection( id=router.id, params=[ModelParameterValue(id=optimize_for.id, value=requested_mode)],)Переключение режимов для отдельных запусков
Переопределите модель в agent.send(), чтобы изменить режим Router для конкретного запуска:
from cursor_sdk import ModelParameterValue, ModelSelection, SendOptionsrun = agent.send( "Handle this complex migration", SendOptions( model=ModelSelection( id="auto-smart", params=[ModelParameterValue(id="optimize_for", value="intelligence")], ), ),)Переопределение модели для отдельного запуска сохраняется и применяется к последующим запускам. Последующие отправки без переопределения продолжают использовать выбранную модель. См. Переопределение модели для отдельного запуска.
Идентификаторы моделей: auto-smart, auto и default
| Выбор | Значение |
|---|---|
auto-smart с optimize_for | Cursor Router. Используйте, если нужен Cost, Balance или Intelligence. |
ModelSelection(id="auto") | Выбранный сервером резервный режим Auto, если конкретная модель отсутствует в каталоге. Если требуется явно указать режим Router, используйте auto-smart. |
Не указывать optimize_for или передавать default | Неподдерживаемый контракт Router. Всегда определяйте допустимые значения и передавайте cost, balanced или intelligence. |
Оплата и пул маршрутизации
- Cost использует классическое поведение Auto и пакетную тарификацию Auto.
- Balance и Intelligence используют Cursor Router и тарифицируются по ставке выбранной модели в рамках вашего тарифа или контракта.
- Базовая модель может различаться от запроса к запросу. Для воспроизводимых сравнений используйте фиксированный ID модели.
- Корпоративные allowlist определяют пул маршрутизации. Блокировка необходимых моделей может отключить Router.
Актуальные ставки и пул маршрутизации см. в разделах Cursor Router и Models & Pricing.
Устранение неполадок: Router недоступен
Если auto-smart отсутствует или режим оптимизации отклонён:
- Вызовите
Cursor.models.list(). - Убедитесь, что
auto-smartприсутствует в результатах. - Убедитесь, что
optimize_forвключает нужное значение (cost,balancedилиintelligence). - Убедитесь, что Router включён для команды, связанной с API-ключом.
- Если вы состоите в нескольких командах, убедитесь, что ключ используется в контексте нужной команды.
- Проверьте политику доступа к моделям команды, если Router недоступен или не может выбрать допустимую базовую модель.
Необработанные словари
Для кода приложений предпочтительнее типизированные датаклассы, поскольку автодополнение в IDE и проверка типов работают лучше. SDK также принимает обычные словари для коротких скриптов или JSON, полученного извне. Ключи в snake_case нормализуются.
from cursor_sdk import Agentwith Agent.create( { "api_key": "crsr_key", "model": {"id": "composer-2.5"}, "local": {"cwd": "."}, }) as agent: ...Agent
Объект, возвращаемый методами Agent.create(), Agent.resume(), client.agents.create() и client.agents.resume().
class Agent: agent_id: str model: ModelSelection | None client: CursorClient def send( self, message: str | Mapping[str, Any] | UserMessage, options: SendOptions | Mapping[str, Any] | None = None, *, idempotency_key: str | None = None, ) -> Run: ... def reload(self) -> None: ... def close(self) -> None: ... def list_messages( self, options: Mapping[str, Any] | None = None ) -> list[AgentMessage]: ... def list_artifacts(self) -> list[SDKArtifact]: ... def download_artifact(self, path: str) -> bytes: ... def archive(self, options: Mapping[str, Any] | None = None) -> None: ... def unarchive(self, options: Mapping[str, Any] | None = None) -> None: ... def delete(self, options: Mapping[str, Any] | None = None) -> None: ...| Участник | Описание |
|---|---|
agent_id | Стабильный идентификатор Agent. agent-<uuid> для локального Agent, bc-<uuid> для облачного. |
model | Текущий типизированный выбор модели. Обновляется после успешной отправки с переопределением модели. |
send | Запускает новый запуск с указанным промптом. Возвращает дескриптор Run. |
reload | Повторно считывает конфигурацию файловой системы (хуки, MCP проекта, субагенты) без удаления. |
close | Закрывает Agent и освобождает ресурсы. |
list_messages | Выводит историю сообщений Agent. |
list_artifacts | Выводит список файлов, созданных Agent (только для облачного Agent; для локального возвращает пустой список). |
download_artifact | Скачивает файл по пути (только для облачного Agent; для локального вызывает исключение). |
archive / unarchive / delete | Управляет жизненным циклом облачного Agent. |
Используйте контекстный менеджер для автоматической очистки:
with Agent.create(model="composer-2.5", local=LocalAgentOptions(cwd=".")) as agent: print(agent.send("Explain this repository").text())Если использовать синхронные помощники Agent.* или Cursor.*, не передавая client=, SDK запускает или повторно использует клиент по умолчанию на уровне модуля. Он автоматически закрывается при завершении процесса, но его также можно закрыть явно:
from cursor_sdk import close_default_clientclose_default_client()Agent.prompt()
Agent.prompt( message: str | Mapping[str, Any] | UserMessage, options: AgentOptions | Mapping[str, Any] | None = None, *, client: CursorClient | None = None,) -> RunResultОдноразовая операция: создаёт Agent, отправляет один запрос, ждёт завершения выполнения и освобождает ресурсы.
from cursor_sdk import Agent, AgentOptions, LocalAgentOptionsresult = Agent.prompt( "What does the auth middleware do?", AgentOptions(model="composer-2.5", local=LocalAgentOptions(cwd=".")),)print(result.result)Асинхронный вариант (предполагается, что AsyncClient уже открыт):
from cursor_sdk import AgentOptions, AsyncAgent, LocalAgentOptionsresult = await AsyncAgent.prompt( "What does the auth middleware do?", AgentOptions(model="composer-2.5", local=LocalAgentOptions(cwd=".")), client=client,)CursorClient
Используйте CursorClient, если вам нужен явный контроль жизненного цикла, пользовательская конечная точка bridge, пользовательские настройки HTTP или несколько рабочих пространств в одном процессе. Client по-прежнему доступен в качестве псевдонима.
from cursor_sdk import CursorClient, LocalAgentOptionswith CursorClient.launch_bridge(workspace=".") as client: with client.agents.create( model="composer-2.5", api_key="crsr_key", local=LocalAgentOptions(cwd="."), ) as agent: print(agent.send("Summarize what this repository does").text())Ресурсы
Клиенты предоставляют пространства имён ресурсов:
| Ресурс | Примеры синхронных методов | Примеры асинхронных методов |
|---|---|---|
agents | client.agents.create(...), client.agents.list(...), client.agents.get(...) | await client.agents.create(...), await client.agents.list(...) |
models | client.models.list() | await client.models.list() |
repositories | client.repositories.list() | await client.repositories.list() |
Методы верхнего уровня, такие как client.create_agent(...) и client.list_agents(...), по-прежнему доступны, но в коде приложений рекомендуется использовать пространства имён ресурсов.
Пользовательские HTTP-клиенты
Клиенты синхронный и асинхронный поддерживают пользовательский клиент httpx для настройки прокси, транспортов и других расширенных параметров HTTP:
from cursor_sdk import CursorClient, DefaultHttpxClientwith CursorClient.launch_bridge( workspace=".", http_client=DefaultHttpxClient(proxy="http://proxy.example.com"),) as client: ...from cursor_sdk import AsyncClient, DefaultAsyncHttpxClientasync with await AsyncClient.launch_bridge( workspace=".", http_client=DefaultAsyncHttpxClient(proxy="http://proxy.example.com"),) as client: ...DefaultHttpxClient и DefaultAsyncHttpxClient используют стандартные для SDK настройки тайм-аута и перенаправлений. Обычные httpx.Client и httpx.AsyncClient используют настройки httpx по умолчанию.
Настройка тайм-аутов и повторных попыток
Оба клиента предоставляют метод with_options(...), который возвращает поверхностную копию с общими настройками подключения и переопределёнными значениями по умолчанию:
short = client.with_options(timeout=5.0, max_retries=2)agent = short.agents.create(model="composer-2.5", local=LocalAgentOptions(cwd="."))Асинхронный вариант:
short_async = async_client.with_options(timeout=5.0, max_retries=2)agent = await short_async.agents.create(model="composer-2.5", local=LocalAgentOptions(cwd="."))Отправка сообщений
Каждый вызов agent.send() возвращает Run. Каждый вызов await async_agent.send() возвращает AsyncRun. Agent сохраняет контекст диалога между запусками; запуск — единица работы для одного промпта.
print(agent.send("Find the bug in src/auth.py").text())# Тот же агент, весь контекст диалога сохраняется.print(agent.send("Fix it and add a regression test").text())Асинхронный вариант:
run = await agent.send("Find the bug in src/auth.py")print(await run.text())run = await agent.send("Fix it and add a regression test")print(await run.text())Чтобы отправить изображения вместе с текстом:
run = agent.send( { "text": "What's in this screenshot?", "images": [{"data": base64_png, "mime_type": "image/png"}], })Также можно использовать вспомогательные датаклассы. SDKImage.from_file(path) считывает файл с диска и кодирует его в base64:
from cursor_sdk import SDKImage, UserMessagerun = agent.send( UserMessage( text="What's in this screenshot?", images=[SDKImage.from_file("screenshot.png")], ))SDKImage.data_image(base64_data, mime_type) и SDKImage.url_image(url) также доступны, если у вызывающей стороны уже есть закодированные байты или удалённый URL.
Запуск
class Run: id: str agent_id: str status: str # "running" | "finished" | "error" | "cancelled" | "expired" result: str model: ModelSelection | None duration_ms: int git: RunGitInfo | None created_at: str | None usage: TokenUsage | None # накопительное значение; свойство активного дескриптора def stream(self) -> Iterator[SDKMessage]: ... def messages(self) -> Iterator[SDKMessage]: ... def events(self) -> Iterator[RunStreamEvent]: ... def iter_text(self) -> Iterator[str]: ... def text(self) -> str: ... def wait(self) -> RunResult: ... def cancel(self) -> None: ... def conversation(self) -> list[ConversationTurn]: ... def conversation_json(self) -> str: ... def observe(self, *, after_offset: str | None = None) -> Iterator[RunStreamEvent]: ... def supports(self, operation: str) -> bool: ... def unsupported_reason(self, operation: str) -> str | None: ... def on_did_change_status( self, listener: Callable[[str], None] ) -> Callable[[], None]: ...run.stream() — псевдоним run.messages(). При прямой итерации по run возвращаются обёртки RunStreamEvent, как и при вызове run.events().
AsyncRun предоставляет те же поля состояния, включая usage. Методы, выполняющие операции ввода-вывода, являются асинхронными: async for message in run.stream(), async for message in run.messages(), async for event in run.events(), async for text in run.iter_text(), await run.text(), await run.wait(), await run.cancel(), await run.conversation(), await run.conversation_json() и async for event in run.observe().
Стриминг
run = agent.send("Find the bug in src/auth.py")for message in run.messages(): if message.type == "assistant": for block in message.message.content: if block.type == "text": print(block.text, end="") elif message.type == "thinking": print(message.text, end="") elif message.type == "tool_call": print(f"[tool] {message.name}: {message.status}") elif message.type == "status": print(f"[status] {message.status}") elif message.type == "usage": print(f"[usage] turn total={message.usage.total_tokens}")Поток выполнения можно использовать только один раз. run.messages(), run.events() и run.iter_text() читают из одного базового потока и продвигают его. После завершения потока выполнение содержит окончательный результат (run.result, run.status, run.usage, run.git, ...). Вызовите run.wait(), чтобы дочитать все оставшиеся события и получить типизированный RunResult.
Ожидание без стриминга
result = run.wait()print(result.status) # "finished" | "error" | "cancelled" | "expired"print(result.result) # итоговый assistant text, если он естьprint(result.model) # итоговый ModelSelection, использованный для этого runprint(result.duration_ms)print(result.usage) # суммарный TokenUsage или None, если недоступноprint(result.git) # RunGitInfo в cloudАсинхронный вариант:
result = await run.wait()Использование токенов
Если среда выполнения предоставляет эти данные, запуски сообщают об использовании токенов. Накопленное значение доступно в run.usage активного дескриптора (во время стриминга или после wait()) либо в result.usage объекта RunResult, возвращаемого run.wait(). Оба содержат объект TokenUsage с суммарными данными по всем ходам, сообщившим об использовании токенов, и оба имеют значение None, если ни один ход не сообщил об этом — например, если отменённый запуск не завершил ни одного хода, среда выполнения не предоставляет данные об использовании или отсоединённый облачный снимок ещё не синхронизировал данные об использовании.
@dataclass(frozen=True)class TokenUsage: input_tokens: int output_tokens: int cache_read_tokens: int cache_write_tokens: int total_tokens: int reasoning_tokens: int | None = None| Поле | Описание |
|---|---|
input_tokens | Токены промпта, отправленные модели. |
output_tokens | Токены, сгенерированные моделью. |
cache_read_tokens | Токены из кэша промпта. |
cache_write_tokens | Токены, записанные в кэш промпта. |
total_tokens | input_tokens + output_tokens + cache_read_tokens + cache_write_tokens. Не включает reasoning_tokens. |
reasoning_tokens | Токены рассуждений — подмножество output_tokens. None, если модель или среда выполнения не предоставила эти данные. |
result = run.wait()if result.usage is not None: print(f"total: {result.usage.total_tokens}") print(f"in: {result.usage.input_tokens}, out: {result.usage.output_tokens}") print( f"cache read/write: {result.usage.cache_read_tokens}/{result.usage.cache_write_tokens}" )else: print("no usage reported for this run")reasoning_tokens уже учитывается в output_tokens, поэтому total_tokens не включает его во избежание двойного подсчёта.
Чтобы получать показатели по каждому шагу по мере поступления, обрабатывайте событие потока usage (SDKUsageMessage). Оно возникает один раз в конце каждого шага, в котором были указаны данные об использовании, и содержит TokenUsage этого шага. run.usage и result.usage остаются накопительными за весь запуск. После шагов потока дескриптор использует эти суммарные значения; в противном случае — данные об использовании из wait() или снимка get_run / list_runs, если bridge их предоставляет.
for message in run.messages(): if message.type == "usage": print(f"turn used {message.usage.total_tokens} tokens")# Или после wait / не обрабатывая сообщения самостоятельно:result = run.wait()print(run.usage, result.usage)Асинхронный вариант: async for message in run.messages() и await run.wait(). run.usage по-прежнему остаётся синхронным свойством AsyncRun.
TokenUsage экспортируется из cursor_sdk (а также to_token_usage / sum_token_usage для расширенных сценариев). JSON в протоколе использует camelCase (inputTokens, …), а датаклассы Python — snake_case.
Количество токенов — это данные, получаемые от среды выполнения; они ничего не говорят о стоимости. Чтобы узнать тарифицируемое использование и стоимость запусков Agent в долларах, вызовите agent.get_usage().
Чтение текстовых выходных данных
iter_text() возвращает текст assistant по мере поступления. text() возвращает окончательный текст из Терминала и ожидает wait(), если run всё ещё выполняется.
for chunk in run.iter_text(): print(chunk, end="")final_text = run.text()Асинхронный вариант:
async for chunk in run.iter_text(): print(chunk, end="")final_text = await run.text()Отмена выполнения
run.cancel()Асинхронный вариант:
await run.cancel()run.cancel() запрашивает отмену активного запуска. Статус меняется на "cancelled", поток событий в реальном времени останавливается, незавершённые вызовы инструментов прекращаются, а run.wait() возвращает status: "cancelled". Частичные выходные данные (текст assistant, созданный к этому моменту) сохраняются в объекте Run.
При отмене запуска, уже находящегося в конечном состоянии ("finished", "error", "cancelled", "expired"), возникает UnsupportedRunOperationError. Если сомневаетесь, проверяйте run.status:
if run.status == "running": run.cancel()Получение статуса run
print(run.id)print(run.status) # "running" | "finished" | "error" | "cancelled" | "expired"stop = run.on_did_change_status(lambda status: print(f"status changed to {status}"))stop() # удалить обработчикturns = run.conversation()run.conversation() возвращает типизированный list[ConversationTurn]. Используйте его для отображения или сохранения структурированной истории без подписки на поток в реальном времени. run.conversation_json() возвращает необработанную JSON-строку.
Для асинхронных запусков используйте await run.conversation() и await run.conversation_json().
Переопределение модели для одного запуска
Параметр model, переданный в agent.send(), переопределяет выбранную Agent модель для этого запуска, а затем сохраняется для последующих запусков: последующие отправки без переопределения продолжают использовать новую модель. Чтобы вернуться к прежней модели, передайте другое значение model или получите текущий выбор из agent.model.
from cursor_sdk import ModelParameterValue, ModelSelection, SendOptionsrun = agent.send( "Plan the refactor", SendOptions( model=ModelSelection( id="composer-2.5", params=[ModelParameterValue(id="fast", value="true")], ), ),)run.model и result.model отражают выбранную для этого запуска модель и не изменяются после его начала.
Переменные среды для одного запуска
Облачные агенты также поддерживают переменные среды для одного запуска. Передайте cloud.env_vars в SendOptions, и значения будут доступны в оболочке агента только в рамках этого запуска — после его завершения они удаляются с VM, и в следующем запуске будут недоступны. Это подходит для учётных данных, которые обновляются между шагами, например краткосрочного токена развёртывания, выпускаемого непосредственно перед тем, как попросить агента его использовать.
from cursor_sdk import CloudSendOptions, SendOptionsrun = agent.send( "Deploy the preview environment", SendOptions( cloud=CloudSendOptions(env_vars={"DEPLOY_TOKEN": mint_short_lived_token()}), ),)Если переменная уровня запуска имеет то же имя, что и переменная уровня агента из env_vars в CloudAgentOptions, в этом запуске приоритет получает значение переменной уровня запуска, а в следующем снова используется значение переменной уровня агента.
Переменные для отдельного запуска работают и при первой отправке. SDK передаёт их при создании агента с областью действия начального запуска, поэтому они не сохраняются в агенте. Как и переменные уровня агента, они шифруются при хранении, а их имена не могут начинаться с CURSOR_.
Переменные среды для отдельного запуска доступны только для облачных агентов и недоступны для агентов, работающих с общедоступными репозиториями. Для локальных агентов процесс агента наследует переменные вашей среды, поэтому задайте их для процесса перед вызовом send().
Режим диалога
Передайте mode="plan" или mode="agent", чтобы указать, должен ли запуск сначала исследовать и составить план или сразу внести изменения. О работе режима планирования в продукте см. в разделе Режим планирования.
Задайте mode в AgentOptions, передаваемом в Agent.create(), чтобы определить режим первого запуска. В последующих вызовах agent.send() не указывайте mode, чтобы сохранить текущий режим диалога, или передайте mode, чтобы переключить режим только для этого запуска.
from cursor_sdk import Agent, AgentOptions, CloudAgentOptions, CloudRepository, SendOptionswith Agent.create( AgentOptions( model="composer-2.5", mode="plan", cloud=CloudAgentOptions( repos=[CloudRepository(url="https://github.com/your-org/your-repo")], ), )) as agent: agent.send("Design the auth refactor").wait() agent.send( "Looks good, start building", SendOptions(mode="agent"), ).wait()Потоковая передача необработанных дельт
Передайте callbacks on_delta и on_step в SendOptions, чтобы получать низкоуровневые обновления. Синхронные callbacks вызываются inline. Асинхронные callbacks могут быть синхронными или асинхронными; возвращаемые значения, допускающие ожидание, ожидаются до обработки следующего события.
from cursor_sdk import SendOptionsdef on_delta(update): if update.type in ("text-delta", "thinking-delta"): print(update.text, end="")run = agent.send( "Refactor the utils module", SendOptions(on_delta=on_delta, on_step=lambda step: print(f"[step] {step.type}")),)run.wait()Конкретные подклассы update и step определены в cursor_sdk.events:
from cursor_sdk.events import TextDeltaUpdate, ToolCallStartedUpdateif isinstance(update, TextDeltaUpdate): print(update.text)Их по-прежнему можно импортировать из cursor_sdk для обратной совместимости, но в новом коде следует импортировать их из cursor_sdk.events.
SendOptions
| Свойство | Тип | Описание |
|---|---|---|
model | str | ModelSelection | Mapping[str, Any] | Переопределение модели для одного сообщения. Если не указано, используется agent.model. Сохраняется после успешной отправки. |
mode | "agent" | "plan" | Переопределение режима диалога для одного сообщения. Если не указано в последующих сообщениях, сохраняется текущий режим диалога. |
mcp_servers | Mapping[str, McpServerConfig] | Встроенные определения серверов MCP. Полностью заменяет серверы, заданные при создании, для этого запуска. |
cloud.env_vars | Mapping[str, str] | Только для облачных агентов. Переменные среды для отдельного запуска, добавляемые для этого запуска и удаляемые после его завершения. Переопределяет env_vars в области действия агента по имени только для этого запуска. |
local.force | bool | Только для локальных агентов. По умолчанию — None (не задано). Укажите True, чтобы завершить зависший активный запуск перед отправкой этого сообщения. В Cloud на стороне сервера возвращается 409 agent_busy, поэтому эквивалент не требуется. |
idempotency_key | str | Необязательный ключ идемпотентности, созданный клиентом для отправки. |
on_step | Callable[[ConversationStep], Any] | Обратный вызов после каждого завершённого шага диалога (текст, рассуждение или пакет вызовов инструментов). |
on_delta | Callable[[InteractionUpdate], Any] | Обратный вызов для каждого необработанного InteractionUpdate. |
Следующие три раздела содержат подробную справку по SDKMessage, InteractionUpdate и ConversationTurn. При первом чтении их можно бегло просмотреть или пропустить; Возобновление работы агентов продолжает изложение.
События потока
run.messages() возвращает типизированные классы данных сообщений SDK. Различайте их по message.type. Если среда выполнения предоставляет их, все сообщения содержат agent_id и run_id.
SDKMessage = ( SDKSystemMessage | SDKUserMessageEvent | SDKAssistantMessage | SDKThinkingMessage | SDKToolUseMessage | SDKStatusMessage | SDKTaskMessage | SDKRequestMessage | SDKUsageMessage | Mapping[str, Any])type | Датакласс | Ключевые поля |
|---|---|---|
"system" | SDKSystemMessage | subtype, model, tools |
"user" | SDKUserMessageEvent | message.content |
"assistant" | SDKAssistantMessage | message.content со значениями TextBlock и ToolUseBlock |
"thinking" | SDKThinkingMessage | text, thinking_duration_ms |
"tool_call" | SDKToolUseMessage | call_id, name, status, args, result, truncated |
"status" | SDKStatusMessage | status, message |
"task" | SDKTaskMessage | status, text |
"request" | SDKRequestMessage | request_id |
"usage" | SDKUsageMessage | usage (TokenUsage) |
SDKToolUseMessage генерируется дважды для большинства вызовов инструментов: сначала с status="running" и заполненным args, затем после завершения — с status="completed" (или "error") и заполненным result. truncated указывает, были ли args или result усечены SDK из-за слишком большого объёма данных.
SDKUsageMessage генерируется один раз в конце каждого шага, для которого было зафиксировано использование токенов, и содержит TokenUsage этого шага. Накопленное значение по всем шагам хранится в run.usage и result.usage. См. Использование токенов.
@dataclass(frozen=True)class SDKUsageMessage: type: Literal["usage"] agent_id: str run_id: str usage: TokenUsageДанные результата (итоговый текст, модель, длительность, суммарное использование токенов, метаданные Git) доступны в объекте Run после завершения потока. Используйте run.wait() для их получения, в том числе result.usage, если среда выполнения сообщила эти данные.
Схема вызова инструмента нестабильна. Полезные нагрузки
argsиresultв событияхtool_callотражают внутреннюю структуру каждого инструмента и могут меняться по мере их развития. Имена инструментов также могут быть переименованы или заменены. Рассматривайтеargsиresultкак нетипизированные данные и разбирайте их с учётом возможных изменений. Обёртка события (type,call_id,name,status) стабильна.
run.events() возвращает низкоуровневые обёртки RunStreamEvent. Используйте его, если нужны смещения, обёртки итоговых результатов или необработанные обновления взаимодействия:
for event in run.events(): print(event.kind, event.offset)Обновления взаимодействия
InteractionUpdate — это тип необработанных дельт, передаваемых в обратный вызов on_delta метода agent.send(). Эти обновления более детализированы, чем события SDKMessage: текст поступает в потоке токен за токеном, а вызовы инструментов сообщают о промежуточном состоянии по мере накопления args.
InteractionUpdate = ( TextDeltaUpdate | ThinkingDeltaUpdate | ThinkingCompletedUpdate | ToolCallStartedUpdate | ToolCallCompletedUpdate | PartialToolCallUpdate | TokenDeltaUpdate | StepStartedUpdate | StepCompletedUpdate | TurnEndedUpdate | UserMessageAppendedUpdate | SummaryUpdate | SummaryStartedUpdate | SummaryCompletedUpdate | ShellOutputDeltaUpdate | UnknownInteractionUpdate | Mapping[str, Any])PartialToolCallUpdate выдаётся, когда модель передаёт аргументы в вызов инструмента до его выполнения. Здесь действует то же предупреждение о стабильности, что и для SDKToolUseMessage.args.
Типы диалогов
Структурированное представление запуска для каждого хода, возвращаемое run.conversation(). Каждый элемент — это оболочка, содержащая дискриминатор типа хода type и типизированную полезную нагрузку в turn.
@dataclass(frozen=True)class ConversationTurn: type: str # "agentConversationTurn" | "shellConversationTurn" turn: AgentConversationTurn | ShellConversationTurn | Mapping[str, Any]@dataclass(frozen=True)class AgentConversationTurn: user_message: Mapping[str, Any] | None = None steps: Sequence[ConversationStep] = ()@dataclass(frozen=True)class ShellConversationTurn: shell_command: ShellCommand | None = None shell_output: ShellOutput | None = NoneConversationStep = ( AssistantConversationStep | ToolCallConversationStep | ThinkingConversationStep | Mapping[str, Any])Определяйте тип по turn.type и получайте полезную нагрузку через turn.turn:
for turn in run.conversation(): if turn.type == "agentConversationTurn": for step in turn.turn.steps: print(step.type) elif turn.type == "shellConversationTurn": print(turn.turn.shell_command, turn.turn.shell_output)run.conversation() в обратных вызовах on_step вызывается для каждого ConversationStep, а не для каждого хода. Шаги диалога с вызовом инструмента содержат полезную нагрузку типа Mapping[str, Any]. Считайте детали полезной нагрузки вызова инструмента нетипизированными данными; см. примечание о стабильности в разделе «События потока».
Возобновление работы агентов
Agent.resume( agent_id: str, options: AgentOptions | Mapping[str, Any] | None = None, *, client: CursorClient | None = None,) -> AgentИспользуйте Agent.resume() или client.agents.resume(), чтобы повторно подключиться к существующему агенту по ID. Распространённые сценарии: повторное подключение к ранее запущенному длительно работающему облачному агенту или продолжение диалога после перезапуска локального процесса. Среда выполнения автоматически определяется по префиксу ID (bc- — облачная, всё остальное — локальная).
agent = Agent.resume("bc-abc123")run = agent.send("Also update the changelog")run.wait()Асинхронный вариант:
agent = await client.agents.resume("bc-abc123")run = await agent.send("Also update the changelog")await run.wait()agent.model имеет значение None при возобновлении, если не передать model повторно. Inline MCP‑серверы не сохраняются между возобновлениями: они часто содержат секреты и существуют только в памяти. Передайте их повторно при возобновлении или используйте файловую конфигурацию MCP (.cursor/mcp.json и local.setting_sources) для серверов, которые должны сохраняться.
Локальное хранение
Локальные агенты сохраняют через bridge состояние диалога и метаданные запусков, поэтому последующие запросы и Agent.resume() работают и после перезапуска процесса. По умолчанию bridge хранит эти данные на диске в отдельном корне состояния для каждого рабочего пространства. Облачные агенты сохраняют данные на стороне сервера, поэтому при возобновлении облачного агента откуда угодно возвращается тот же диалог.
Локальное хранение привязано к рабочему пространству. Если bridge работает как долгоживущий sidecar или подпроцесс, укажите для него то же рабочее пространство, что и для агента, чтобы локальные вызовы list, get и resume находили нужных агентов. Задайте его один раз в клиенте и передайте cwd в локальные вызовы list и get:
from cursor_sdk import CursorClientwith CursorClient.launch_bridge(workspace="/path/to/repo") as client: agents = client.agents.list(runtime="local", cwd="/path/to/repo") info = client.agents.get(agents.items[0].agent_id, cwd="/path/to/repo")Просмотр агентов и запусков
Используйте CursorClient для API получения списков, отдельных объектов и пагинации.
from cursor_sdk import CursorClientwith CursorClient.launch_bridge(workspace=".") as client: agents = client.agents.list(runtime="local", cwd=".") for agent_info in agents.auto_paging_iter(): print(agent_info.agent_id) info = client.agents.get(agents.items[0].agent_id) runs = client.agents.list_runs(info.agent_id) run = client.agents.get_run(runs.items[0].id)Асинхронный вариант:
agents = await client.agents.list(runtime="local", cwd=".")async for agent_info in agents.auto_paging_iter(): print(agent_info.agent_id)info = await client.agents.get(agents.items[0].agent_id)runs = await client.agents.list_runs(info.agent_id)run = await client.agents.get_run(runs.items[0].id)Используйте agent.list_messages() с Agent, чтобы получить историю сообщений. Agent.messages.list(agent_id) — удобный типизированный вариант того же вызова, если у вас есть только ID.
Конечные точки списков возвращают ListResult[T]. Используйте .items и .next_cursor напрямую, перебирайте элементы текущей страницы через for item in page или все страницы через .auto_paging_iter(). Асинхронные конечные точки списков возвращают AsyncListResult[T]; async for item in page перебирает элементы текущей страницы, а async for item in page.auto_paging_iter() — все страницы в наборе результатов.
SDKAgentInfo
Структура метаданных, которую возвращают Agent.list(), Agent.get(), client.agents.list() и client.agents.get().
@dataclass(frozen=True)class SDKAgentInfo: agent_id: str name: str summary: str last_modified: str | None = None status: str | None = None # "running" | "finished" | "error" created_at: str | None = None archived: bool = False runtime: Literal["local", "cloud"] | None = None cwd: str = "" env: CloudEnvironment | None = None repos: Sequence[str] = () metadata: Mapping[str, str] = {} # из CloudAgentOptions.metadata; для локальных агентов пустоЖизненный цикл облачного агента
Облачные агенты остаются в рабочем пространстве вашей команды, пока вы не архивируете или не удалите их. client.agents.list(runtime="cloud") по умолчанию не показывает архивированных агентов; передайте include_archived=True, чтобы увидеть их. Отфильтруйте по pr_url, чтобы найти агента, создавшего конкретный pull request.
# По идентификатору, дескриптор агента не требуется:Agent.archive(agent_id)Agent.unarchive(agent_id)Agent.delete(agent_id)# Через явный объект клиента:client.agents.archive(agent_id)client.agents.unarchive(agent_id)client.agents.delete(agent_id)# При наличии существующего дескриптора агента:agent.archive()agent.unarchive()agent.delete()archive помечает Agent как удалённый, сохраняя transcript доступным для чтения. unarchive восстанавливает его. delete удаляет навсегда; последующие операции чтения возвращают NotFoundError.
Асинхронные методы lifecycle имеют те же имена и поддерживают await.
agent.get_usage()
Получает данные о тарифицируемом использовании токенов и стоимости запусков агента в долларах. Облачные агенты возвращают разбивку по запускам, локальные агенты — по шагам. Передайте run_id, чтобы ограничить результат одной записью: для облачных агентов — идентификатор запуска run-<uuid>, для локальных агентов — идентификатор из предыдущего get_usage().runs[].run_id.
usage = agent.get_usage()print(f"tokens: {usage.usage.total_tokens}")if usage.cost is not None: print(f"charged: ${usage.cost.charged_cents / 100:.2f}")for run in usage.runs: print(run.run_id, run.usage.total_tokens)@dataclass(frozen=True)class AgentUsage: usage: TokenUsage # сумма по всем `runs` runs: Sequence[RunUsage] = () cost: UsageCost | None = None # сумма по всем `runs`@dataclass(frozen=True)class RunUsage: run_id: str usage: TokenUsage cost: UsageCost | None = None@dataclass(frozen=True)class UsageCost: raw_cost_cents: float # стоимость токенов модели без скидок; 0 при оплате за запрос charged_cents: float # списанная сумма с учётом скидок и ставки токенов CursorСтоимость учитывает скидки и может окончательно сформироваться не сразу после завершения запуска; до этого cost имеет значение None. Для использования, включённого в тариф, BYOK и кредитных грантов charged_cents равно 0.0.
Это отличается от Использования токенов: run.usage — текущее число токенов в одном запуске, а get_usage() — запись о выставленной оплате по всем запускам агента. Для асинхронных агентов этому соответствует await agent.get_usage(). AgentUsage, RunUsage и UsageCost экспортируются из cursor_sdk.
Пространство имён Cursor
Получение данных аккаунта и каталога. Методы синхронизации принимают необязательный api_key; в противном случае используется CURSOR_API_KEY.
from cursor_sdk import Cursorme = Cursor.me()models = Cursor.models.list()repositories = Cursor.repositories.list()Эквивалент с явным указанием клиента:
me = client.me()models = client.models.list()repositories = client.repositories.list()Асинхронный вариант:
from cursor_sdk import AsyncCursorme = await AsyncCursor.me(client=client)models = await AsyncCursor.models.list(client=client)repositories = await AsyncCursor.repositories.list(client=client)Используйте Cursor.models.list(), чтобы узнать допустимые идентификаторы моделей и параметры конкретных моделей перед вызовом Agent.create() или agent.send(). Параметры зависят от модели. Среди распространённых примеров — уровень усилий рассуждений и параметр optimize_for Cursor Router для auto-smart.
Каталог зависит от аккаунта и команды. Cursor Router отображается как auto-smart, только если Router доступен для команды API-ключа. См. Cursor Router.
models = Cursor.models.list()composer = next((model for model in models if model.id == "composer-2.5"), None)print(composer.parameters if composer else [])# [# ModelParameterDefinition(# id="fast",# display_name="Fast",# values=(# ModelParameterDefinitionValue(value="false"),# ModelParameterDefinitionValue(value="true", display_name="Fast"),# ),# ),# ]Предустановленные variants каждой SDKModel уже содержат корректные params, поэтому их можно скопировать в ModelSelection.
Если целевая модель недоступна и вам нужны Cost, Balance или Intelligence, предпочтительнее явно выбрать Router (auto-smart + optimize_for). Используйте ModelSelection(id="auto") только если нужен Auto, выбранный сервером, без выбора режима Router. Для Cursor Router всегда явно указывайте optimize_for.
Cursor.repositories.list() возвращает SCM-репозитории (GitHub, GitLab, Bitbucket, Azure DevOps — в зависимости от подключённых сервисов), доступные облачным агентам в аккаунте или команде, от имени которых выполняется вызов. Каждый элемент содержит url. Используйте их для заполнения CloudAgentOptions.repos.
MCP‑серверы
В зависимости от среды выполнения агенты могут получать MCP‑серверы из встроенных определений, настроек проекта или пользователя, плагинов и конфигурации, управляемой через дашборд.
from cursor_sdk import ( Agent, AgentOptions, HttpMcpServerConfig, LocalAgentOptions, McpAuth, StdioMcpServerConfig,)agent = Agent.create( AgentOptions( model="composer-2.5", local=LocalAgentOptions(cwd="."), mcp_servers={ "docs": HttpMcpServerConfig( url="https://example.com/mcp", auth=McpAuth(client_id="client-id", scopes=["read", "write"]), ), "filesystem": StdioMcpServerConfig( command="npx", args=["-y", "@modelcontextprotocol/server-filesystem", "."], ), }, ))Также поддерживаются плоские словари ({"type": "http", "url": ...} и {"type": "stdio", "command": ...}) — для удобства при написании коротких скриптов.
Что загружается
Локальные агенты загружают серверы максимум из пяти источников. При совпадении имён используется первый найденный источник:
mcp_serversвagent.send(). Полностью заменяет серверы, заданные при создании, для этого запуска (без объединения).mcp_serversвAgent.create(). Используется, если для отдельной отправки не задано переопределение.- Серверы плагинов, если
local.setting_sourcesвключает"plugins". - Серверы проекта из
.cursor/mcp.json, еслиlocal.setting_sourcesвключает"project". - Серверы пользователя из
~/.cursor/mcp.json, еслиlocal.setting_sourcesвключает"user".
Без local.setting_sources загружаются только серверы, заданные напрямую. Если локальному MCP‑серверу требуется вход через OAuth, SDK может использовать сохранённые данные входа из приложения Cursor, но не может открыть браузер для авторизации.
Облачные агенты загружают серверы из:
mcp_serversвagent.send(). Полностью заменяет серверы, заданные при создании, для этого запуска (без объединения).mcp_serversвAgent.create(). Используется, если для отдельной отправки не задано переопределение.- Ваших пользовательских и командных MCP‑серверов с cursor.com/agents.
Если сервер, заданный напрямую, не включает auth или headers, а вы ранее авторизовали URL этого сервера на cursor.com/agents, запуски, аутентифицированные персональным API-токеном, автоматически повторно используют эти OAuth-токены. API-ключи сервисных аккаунтов не могут использовать пользовательскую аутентификацию, так как не связаны с пользователем.
local.setting_sources не применяется к облачным агентам.
Облако
Облачные агенты также поддерживают аутентифицированные конфигурации MCP, заданные inline. Облачный MCP поддерживает транспорты HTTP и stdio. Используйте HTTP headers для статических API-ключей или Bearer-токенов. Используйте HTTP auth для серверов, защищённых OAuth. Используйте stdio env, если сервер работает в облачной VM и считывает учётные данные из переменных среды.
from cursor_sdk import ( Agent, AgentOptions, CloudAgentOptions, CloudRepository, HttpMcpServerConfig, StdioMcpServerConfig,)agent = Agent.create( AgentOptions( model="composer-2.5", cloud=CloudAgentOptions( repos=[CloudRepository(url="https://github.com/your-org/your-repo")], ), mcp_servers={ "linear": HttpMcpServerConfig( url="https://mcp.linear.app/mcp", headers={"Authorization": "Bearer linear_pat_xxx"}, ), "github": StdioMcpServerConfig( command="npx", args=["-y", "@modelcontextprotocol/server-github"], env={"GITHUB_TOKEN": "ghp_xxx"}, ), }, ))- HTTP
headersиauthобрабатываются бэкендом Cursor. Конфиденциальные поля скрываются и не попадают в VM. - Значения Stdio
envпередаются в VM, поскольку сервер работает там. Обращайтесь с ними как с любым другим секретом среды выполнения. - OAuth для MCP‑серверов, настроенных на cursor.com/agents, остаётся пользовательским, даже для серверов уровня команды.
Полный формат конфигурации см. в разделе MCP, а особенности работы в облаке — в разделе возможности облачного агента.
Субагенты
Определяйте именованных субагентов, которых основной агент может запускать с помощью инструмента Agent. Передавайте их прямо в вызове:
from cursor_sdk import Agent, AgentDefinition, AgentOptions, LocalAgentOptionsagent = Agent.create( AgentOptions( model="composer-2.5", local=LocalAgentOptions(cwd="."), agents={ "code-reviewer": AgentDefinition( description="Expert code reviewer for quality and security.", prompt="Review code for bugs, security issues, and proven approaches.", model="inherit", ), "test-writer": AgentDefinition( description="Writes tests for code changes.", prompt="Write comprehensive tests for the given code.", ), }, ))Субагенты, добавленные в репозиторий по пути .cursor/agents/*.md (с фронтматтером name, description и необязательным model), также распознаются. Встроенные определения переопределяют файловые определения с тем же именем.
Вложенные субагенты
Субагенты могут создавать собственных субагентов с ограниченной глубиной вложенности. Когда субагент использует инструмент Agent, он обращается к тому же исполнителю субагентов, что и родительский агент. Поэтому родительский агент может делегировать задачу субагенту, который передаст её дальше. На каждом уровне доступен один и тот же набор именованных субагентов. Агент верхнего уровня и его непосредственные субагенты могут запускать субагентов, но субагент, запущенный другим субагентом, не может запускать новых.
Ограничение набора инструментов
tools задаёт allowlist встроенных инструментов, доступных модели; disallowed_tools исключает указанные инструменты, оставляя остальные, включая добавленные на платформу после релиза вашей версии SDK. Пока оба параметра доступны только для локальных агентов и не сохраняются в Agent: при возобновлении передайте их снова, чтобы сохранить ограничение.
from cursor_sdk import Agent, AgentOptions, LocalAgentOptions# Агент только для чтения: ему доступны только эти инструменты.reader = Agent.create( AgentOptions( model="composer-2.5", tools=["read", "grep", "glob", "ls"], local=LocalAgentOptions(cwd="."), ))# Всё, кроме доступа к командной оболочке.no_shell = Agent.create( AgentOptions( model="composer-2.5", disallowed_tools=["shell"], local=LocalAgentOptions(cwd="."), ))- Если не указывать
tools, используется стандартный набор инструментов для выбранной модели;tools=[]не предоставляет встроенных инструментов, поэтому модель может отвечать только текстом. - Оба поля принимают общедоступные имена (
"read","edit","task","webSearch", ...) и группы возможностей"shell"и"mcp". Неизвестные имена приводят кBadRequestErrorпри создании. - Запрет имеет приоритет: чтобы инструмент был доступен, он должен быть указан в
tools(если поле задано) и отсутствовать вdisallowed_tools. - Запрет
"mcp"также убирает пользовательские инструменты. Запрет"task"не позволяет использовать субагентов; в противном случае субагенты сохраняют собственные специально подобранные наборы инструментов.
Пользовательские инструменты
Пользовательские инструменты позволяют сделать функции Python доступными локальным агентам без развёртывания отдельного сервера MCP. Передайте их в LocalAgentOptions.custom_tools.
from cursor_sdk import Agent, CustomTool, CustomToolContext, LocalAgentOptionsdef get_deployment_status(args, context: CustomToolContext): service = args["service"] return f"Service {service} is healthy."with Agent.create( model="composer-2.5", local=LocalAgentOptions( cwd=".", custom_tools={ "get_deployment_status": CustomTool( description="Look up the current deployment status for a service.", input_schema={ "type": "object", "properties": { "service": {"type": "string", "description": "Service name"}, }, "required": ["service"], }, execute=get_deployment_status, ), }, ),) as agent: agent.send("Is the checkout service healthy?").wait()execute получает разобранные аргументы и CustomToolContext с tool_call_id, если он доступен. Он может возвращать строку, значение, совместимое с JSON, или mapping со списком content. Пользовательские инструменты поддерживаются только локальными агентами.
Хуки
Хуки настраиваются только через файлы. Программные обратные вызовы для хуков не поддерживаются. Хуки определяют границы политик проекта, а не настраиваются для каждого отдельного запуска.
- Локально: Добавьте
.cursor/hooks.jsonв репозиторий, указанный вlocal.cwd, или~/.cursor/hooks.jsonдля хуков уровня пользователя. - Облако: Закоммитьте
.cursor/hooks.jsonи связанные с ним скрипты в репозиторий, указанный вcloud.repos. Облачные агенты, созданные через SDK, автоматически загружают хуки проекта. На тарифах Enterprise они также выполняют хуки команды и хуки, управляемые на уровне организации.
Сведения о формате конфигурации см. в разделе Хуки, а о поведении в облаке — в разделе Поддержка хуков Cloud Agents.
Артефакты
Просматривайте и скачивайте файлы из рабочего пространства агента.
@dataclass(frozen=True)class SDKArtifact: path: str size_bytes: int = 0 updated_at: str = ""from pathlib import Pathartifacts = agent.list_artifacts()for artifact in artifacts: print(artifact.path, artifact.size_bytes)# Скачать один артефакт на диск.content = agent.download_artifact(artifacts[0].path)Path("review.md").write_bytes(content)Асинхронные агенты предоставляют методы await agent.list_artifacts() и await agent.download_artifact(path).
Поддержка артефактов зависит от среды выполнения. Локальные агенты SDK возвращают пустой список при вызове list_artifacts() и генерируют исключение при вызове download_artifact().
Управление ресурсами
Всегда закрывайте Agent после завершения работы с ними. Самый удобный паттерн для синхронного кода — контекстный менеджер:
from cursor_sdk import Agent, LocalAgentOptionswith Agent.create(model="composer-2.5", local=LocalAgentOptions(cwd=".")) as agent: agent.send("Summarize the repository").wait()Чтобы явно освободить ресурс:
agent.close()Асинхронные Agent и клиенты поддерживают асинхронные менеджеры контекста и очистку ресурсов с помощью await:
from cursor_sdk import AsyncClient, LocalAgentOptionsasync with await AsyncClient.launch_bridge(workspace=".") as client: async with await client.agents.create( model="composer-2.5", local=LocalAgentOptions(cwd="."), ) as agent: run = await agent.send("Summarize the repository") await run.wait()Чтобы явно освободить ресурс:
await agent.close()await client.aclose()Синхронный клиент по умолчанию уровня модуля автоматически закрывается при завершении процесса. В долгоживущих процессах его можно явно закрыть и сбросить:
from cursor_sdk import close_default_clientclose_default_client()Справочник по конфигурации
Python SDK поддерживает вспомогательные dataclass и обычные словари. В dataclass используются поля Python в формате snake_case; они предпочтительны для кода приложения.
AgentOptions
| Свойство | Тип | По умолчанию | Описание |
|---|---|---|---|
model | str | ModelSelection | Mapping[str, Any] | Обязательно для локального Agent; для облачного используется значение по умолчанию, определённое сервером | Используемая модель. См. ModelSelection. |
api_key | str | переменная окружения CURSOR_API_KEY | Пользовательский API-ключ или ключ сервисного аккаунта. Ключи администратора команды пока не поддерживаются. |
name | str | Генерируется автоматически | Понятное человеку имя Agent, отображаемое в client.agents.list() / client.agents.get(). |
local | LocalAgentOptions | Mapping[str, Any] | None | Конфигурация локального Agent. Укажите для создания локального Agent. |
cloud | CloudAgentOptions | Mapping[str, Any] | None | Конфигурация облачного Agent. Укажите для создания облачного Agent. |
mcp_servers | Mapping[str, McpServerConfig] | None | Встроенные определения серверов MCP. |
agents | Mapping[str, AgentDefinition | Mapping[str, Any]] | None | Определения субагентов. |
tools | Sequence[str] | Набор инструментов по умолчанию | Модели предоставляются только перечисленные встроенные инструменты. [] означает отсутствие встроенных инструментов; модель может отвечать только текстом. Только для локальных Agent. |
disallowed_tools | Sequence[str] | None | Исключает перечисленные встроенные инструменты; все остальные остаются доступными. При использовании вместе с tools приоритет имеет запрет. Только для локальных Agent. |
agent_id | str | Генерируется автоматически | Постоянный ID Agent. Укажите, чтобы сохранить стабильный ID между вызовами. |
idempotency_key | str | Генерируется автоматически для облачного Agent | Необязательный ключ идемпотентности, сгенерированный клиентом. Только для облачного Agent. |
mode | "agent" | "plan" | None | Начальный режим диалога для первого запуска Agent. Если не указан, сервер запускается в режиме Agent. См. Режим диалога. |
LocalAgentOptions
| Свойство | Тип | По умолчанию | Описание |
|---|---|---|---|
cwd | str | os.PathLike | None | Основной рабочий каталог. Списки из нескольких элементов не принимаются; для нескольких корневых каталогов используйте dirs. |
dirs | Sequence[str | os.PathLike] | None | Дополнительные папки рабочего пространства для конфигураций с несколькими корневыми каталогами. Объединяются с cwd, чтобы правила, навыки и контекст рабочего пространства загружались из всех путей. |
setting_sources | Sequence[SettingSource] | None | Уровни настроек окружения: "project", "user", "team", "mdm", "plugins" или "all". |
sandbox_options | SandboxOptions | Mapping[str, Any] | None | Параметры локальной песочницы. |
store | LocalAgentStoreConfig | Mapping[str, Any] | None | Конфигурация локального хранилища, передаваемая bridge. |
auto_review | bool | None | Направлять локальные вызовы инструментов через Auto-review, если подключённый бэкенд поддерживает эту функцию. |
custom_tools | Mapping[str, CustomTool | Mapping[str, Any]] | None | Пользовательские инструменты, доступные локальным Agent. |
CloudAgentOptions
| Свойство | Тип | По умолчанию | Описание |
|---|---|---|---|
env | CloudEnvironment | Mapping[str, Any] | None | Среда выполнения. Если параметр не указан, сервер использует облачные VM, размещённые в Cursor. pool и machine указывают на self-hosted воркеры, которые вы запускаете. |
repos | Sequence[CloudRepository | Mapping[str, Any]] | None | Репозитории для клонирования в VM. Не указывайте ни repos, ни env, чтобы создать Agent без репозитория с пустым рабочим пространством. Передайте pr_url в репозитории, чтобы прикрепить Agent к существующему PR. |
work_on_current_branch | bool | None | Отправлять коммиты в существующую ветку вместо создания новой. Сервер считает неуказанное значение равным False. |
auto_create_pr | bool | None | Открывать PR после завершения запуска. Сервер считает неуказанное значение равным False. |
open_as_cursor_github_app | bool | True для ключей сервисных аккаунтов, False для пользовательских ключей | Открывать PR от имени Cursor GitHub App, а не владельца API-ключа. Итоговое значение возвращается при создании, получении и выводе списка. |
skip_reviewer_request | bool | None | Не запрашивать вызывающего пользователя в качестве ревьюера PR. Сервер считает неуказанное значение равным False. |
env_vars | Mapping[str, str] | None | Переменные среды для облачных Agent в рамках сессии. |
metadata | Mapping[str, str] | None | Строковые теги, принадлежащие вызывающей стороне и сохраняемые в облачном Agent. См. Метаданные Agent. |
AgentDefinition
| Свойство | Тип | По умолчанию | Описание |
|---|---|---|---|
description | str | обязательно | Когда следует использовать этого субагента. Отображается родительскому Agent, чтобы тот знал, когда его запускать. |
prompt | str | обязательно | Системный промпт субагента. |
model | str | ModelSelection | Mapping[str, Any] | "inherit" | None | Переопределение модели. И None, и "inherit" используют выбор модели родительского Agent. |
mcp_servers | Sequence[str | AgentDefinitionMcpServer | Mapping[str, Any]] | None | MCP‑серверы, доступные этому субагенту. Имена ссылаются на серверы из mcp_servers родительского Agent. |
Пользовательский инструмент
@dataclassclass CustomTool: execute: Callable[[Mapping[str, Any], CustomToolContext], Any] description: str | None = None input_schema: Mapping[str, Any] | None = Noneclass CustomToolContext: tool_call_id: str | None = NoneModelSelection
@dataclass(frozen=True)class ModelSelection: id: str params: Sequence[ModelParameterValue] = ()@dataclass(frozen=True)class ModelParameterValue: id: str value: strid — идентификатор модели (например, "composer-2.5" или "auto-smart"). params содержит параметры конкретной модели, например уровень усилий для рассуждений или optimize_for Router'а. Используйте Cursor.models.list(), чтобы узнать допустимые идентификаторы, определения параметров и предустановленные варианты для вашего аккаунта. Подробнее о контракте выбора Router см. в разделе Cursor Router.
McpServerConfig
from cursor_sdk.types import McpServerConfig@dataclass(frozen=True)class HttpMcpServerConfig: url: str type: Literal["http", "sse"] | str = "http" headers: Mapping[str, str] | None = None auth: McpAuth | Mapping[str, Any] | None = None@dataclass(frozen=True)class SseMcpServerConfig(HttpMcpServerConfig): type: Literal["sse"] = "sse"@dataclass(frozen=True)class StdioMcpServerConfig: command: str args: Sequence[str] | None = None env: Mapping[str, str] | None = None cwd: str | os.PathLike | None = None # только для локального режима; в облаке это поле не поддерживается@dataclass(frozen=True)class McpAuth: client_id: str client_secret: str | None = None scopes: Sequence[str] = ()Для HTTP-серверов, работающих в облаке, headers и auth обрабатываются бэкендом Cursor. Конфиденциальные поля скрываются до того, как их увидит VM. Для stdio-серверов в облаке значения env передаются в VM (обращайтесь с ними как с любыми другими секретами среды выполнения).
UserMessage
@dataclass(frozen=True)class UserMessage: text: str images: Sequence[SDKImage | Mapping[str, Any]] | None = NoneСтруктурированная форма аргумента message метода agent.send(). Используйте её для отправки изображений вместе с текстом.
SDKImage
@dataclass(frozen=True)class SDKImage: url: str | None = None data: str | None = None mime_type: str | None = None dimension: SDKImageDimension | Mapping[str, Any] | None = None @classmethod def from_url(cls, url: str, dimension=None) -> SDKImage: ... @classmethod def from_data(cls, data: bytes | str, mime_type: str, dimension=None) -> SDKImage: ... @classmethod def url_image(cls, url: str, dimension=None) -> SDKImage: ... @classmethod def data_image(cls, data: str, mime_type: str, dimension=None) -> SDKImage: ... @classmethod def from_file(cls, path, *, mime_type=None, dimension=None) -> SDKImage: ...Передайте либо удалённый url, либо данные data в формате base64 с mime_type. from_data() принимает байты или строку в формате base64. from_file() считывает файл с диска и кодирует его в base64.
SettingSource
SettingSource доступен из cursor_sdk.types.
from cursor_sdk.types import SettingSourceОпределяет, какие слои настроек на диске загружает локальный Agent. Облачные Agent всегда загружают project, team и plugins и игнорируют это поле.
| Значение | Источник |
|---|---|
"project" | .cursor/ в рабочем пространстве |
"user" | ~/.cursor/ |
"team" | Настройки команды, синхронизированные с дашборда |
"mdm" | Корпоративные настройки, управляемые MDM |
"plugins" | Настройки, предоставляемые плагинами |
"all" | Сокращение для всех перечисленных выше |
ListResult
@dataclass(frozen=True)class ListResult(Generic[T]): items: list[T] next_cursor: str = "" def to_dict(self) -> dict[str, Any]: ... def has_next_page(self) -> bool: ... def next_page_info(self) -> dict[str, str]: ... def get_next_page(self) -> ListResult[T]: ... def auto_paging_iter(self) -> Iterator[T]: ...Возвращается методами client.agents.list(), client.agents.list_runs() и Agent.list(). next_cursor пуст, если страниц больше нет. Асинхронные конечные точки для получения списков возвращают AsyncListResult[T] с эквивалентными awaitable-методами.
Ошибки
Все ошибки SDK наследуются от CursorAgentError. CursorSDKError — корневой псевдоним для обратной совместимости со старым вызывающим кодом. Используйте is_retryable и retry_after для реализации логики повторных попыток.
class CursorAgentError(Exception): message: str code: str | None status: int | None status_code: int | None details: list[Mapping[str, Any]] is_retryable: bool cause: BaseException | None request_id: str | None headers: Mapping[str, str] retry_after: str | None| Ошибка | Когда |
|---|---|
AuthenticationError | Недействительный API-ключ или вход в систему не выполнен. |
PermissionDeniedError | У аутентифицированного вызывающего объекта нет права доступа к запрошенной операции. |
RateLimitError | Слишком много запросов или превышены лимиты использования. |
ConfigurationError | Недопустимая модель, отсутствует обязательная конфигурация или неверные параметры запроса. |
AgentBusyError | Отправка уточнения, когда у Agent уже есть запуск в состоянии CREATING или RUNNING (HTTP 409, код agent_busy). |
BadRequestError | Неверно сформированный запрос. |
IntegrationNotConnectedError | Создание облачного Agent для репозитория, SCM-провайдер которого не подключён. |
NetworkError | Сервис недоступен или произошёл сбой сети. |
APITimeoutError | Превышено время ожидания запроса. |
InternalServerError | Сервис Cursor вернул серверную ошибку. |
NotFoundError | Запрошенный ресурс не найден. |
AgentNotFoundError | Agent не существует или не виден в текущем рабочем каталоге. |
UnsupportedRunOperationError | Операция запуска не поддерживается в текущем состоянии запуска. |
Повторные попытки с экспоненциальной задержкой
is_retryable и retry_after определяют логику повторных попыток на стороне вызывающего кода. Если retry_after задан, сервер передаёт его в виде строки в формате HTTP: количество секунд или HTTP-дата.
import timefrom cursor_sdk import Agent, AgentOptions, CursorAgentError, LocalAgentOptions, RateLimitErrorfor attempt in range(3): try: result = Agent.prompt( "Audit the auth middleware for missing input validation", AgentOptions(model="composer-2.5", local=LocalAgentOptions(cwd=".")), ) break except RateLimitError as err: time.sleep(float(err.retry_after) if err.retry_after else 2**attempt) except CursorAgentError as err: if not err.is_retryable: raise time.sleep(2**attempt)Каждый CursorAgentError содержит request_id, если сервер его вернул. Логируйте его при отображении ошибки, чтобы службе поддержки было проще разобраться в причине сбоя.
IntegrationNotConnectedError
class IntegrationNotConnectedError(ConfigurationError): provider: str # например: "github", "gitlab", "azuredevops" help_url: str # ссылка на дашборд для повторного подключенияИспользуйте help_url, чтобы направить пользователя к нужному сценарию повторного подключения. Новые провайдеры можно добавлять без релиза SDK.
AgentBusyError
Облачные Agent поддерживают только один активный запуск одновременно. AgentBusyError возникает, если вызвать agent.send() (или иным образом создать запуск), пока другой запуск того же Agent всё ещё находится в статусе CREATING или RUNNING.
is_retryable имеет значение False. Немедленная повторная попытка будет завершаться ошибкой, пока активный запуск не перейдёт в конечный статус или не будет отменён. Другие ответы 409, например agent_archived, вместо этого вызывают ConfigurationError.
Дождитесь завершения активного запуска, отмените его с помощью run.cancel() или опрашивайте Agent.list_runs() перед повторной отправкой:
from cursor_sdk import Agent, AgentBusyErroragent = Agent.resume("bc-00000000-0000-0000-0000-000000000001")try: agent.send("Also add tests for the auth middleware.")except AgentBusyError: runs = Agent.list_runs(agent.agent_id, {"runtime": "cloud", "limit": 1}) active = runs.items[0] if runs.items else None if active is not None and active.status == "running": active.cancel() agent.send("Also add tests for the auth middleware.")Локальные Agent не генерируют AgentBusyError. Передайте local={"force": True} в send(), чтобы завершить зависший локальный запуск перед запуском нового.
UnsupportedRunOperationError
class UnsupportedRunOperationError(ConfigurationError): operation: strВозникает, когда операция Run недоступна для текущего запуска. Наиболее распространённый случай — вызов run.cancel() для уже завершённого запуска.
run.supports(operation) и run.unsupported_reason(operation) сообщают, поддерживается ли операция с указанным именем ("stream", "wait", "cancel", "conversation") на уровне SDK, и не проверяют состояние запуска. Используйте run.status, чтобы проверять состояние перед вызовами, зависящими от него.
Устранение неполадок
Задайте CURSOR_SDK_LOG=debug (или info), чтобы добавить обработчик stderr к внутреннему логгеру SDK. SDK настраивает только свой логгер cursor_sdk, поэтому это не повлияет на настройку логирования основного приложения.
CURSOR_SDK_LOG=debug python my_script.pyБинарный файл bridge, входящий в комплект, устанавливается как cursor-sdk-bridge в PATH вместе с пакетом. Запустите его напрямую, чтобы убедиться, что в вашем wheel поставляется нужная сборка:
cursor-sdk-bridge --helpИзвестные ограничения
- Схемы payload вызовов инструментов намеренно не имеют строгой типизации.
- Inline MCP‑серверы не сохраняются между вызовами
Agent.resume(). При необходимости передайте их снова при возобновлении. - Пользовательские инструменты (
local.custom_tools) и ограничения набора инструментов (tools,disallowed_tools) доступны только для локальных Agent. Ограничения не сохраняются у Agent; передайте их снова при возобновлении. - Скачивание артефактов не реализовано для локальных Agent.
local.setting_sources(а также пути к файловым MCP‑серверам и субагентам, которые он контролирует) не применяется к облачным Agent. Cloud всегда загружаетproject,teamиplugins.- Хуки поддерживаются только в файловом виде (
.cursor/hooks.json). Программные callback-функции не поддерживаются.