// Cursor `agent.v1` wire schema (partial), reverse-engineered for Roder.
//
// PROVENANCE
// ----------
// Field numbers and message shapes here are transcribed from the authoritative
// Cursor app bundle protobuf-es schema, found in:
//
//   /Applications/Cursor.app/Contents/Resources/app/extensions/cursor-agent/
//     dist/cursor-agent-worker/dist/main.js   (generated protobuf-es output)
//
// and cross-checked against a live `cursor-agent` `agent.v1.AgentService/Run`
// capture (see docs/roder-cursor-agent-runtime-protocol.md). This is the SAME
// source the existing encoders in `src/proto.rs` were derived from.
//
// SCOPE / STATUS
// --------------
// This is NOT the complete Cursor schema. It covers only the messages Roder
// encodes today plus the full inline-image contract. Messages/fields Roder does
// not send are included where known for context and marked accordingly. Field
// numbers are wire-truth; field *names* mirror the bundle where recovered and
// are otherwise best-effort.
//
// This file is reference/codegen material. Roder hand-rolls the encoding in
// `src/proto.rs` (no protoc build step). Keep the two in sync: every field
// number below should match a comment in `src/proto.rs`.
//
// Verified live: text turns, agentic tool loop (read/write/shell/search), and
// the agent mode field. Inline images are encoded per the bundle schema and
// covered by unit tests, but had not been confirmed against a live image turn
// at time of writing.

syntax = "proto3";

package agent.v1;

import "google/protobuf/struct.proto";  // google.protobuf.Value / Struct

// ===========================================================================
// Run request
// ===========================================================================

// AgentService/Run initial client message payload.
// Roder encodes: f1, f2, f5, f9, f12, f16, f19 (see encode_agent_run_request).
message AgentRunRequest {
  bytes conversation_state = 1;                 // sent empty by Roder
  ConversationAction action = 2;
  // Client-advertised tool definitions the model may call. Roder maps its own
  // ToolSpecs (and configured MCP servers) into McpToolDefinition entries here.
  // Historically Roder sent this empty; an empty McpTools advertises no tools.
  McpTools mcp_tools = 4;
  string conversation_id = 5;
  // Cursor-hosted MCP server filesystem options. Not sent by Roder (Roder
  // executes tools client-side rather than via Cursor-hosted servers).
  McpFileSystemOptions mcp_file_system_options = 6;
  RequestedModel requested_model = 9;
  uint32 unknown_f12 = 12;                      // sent as 0 by Roder
  string conversation_id_dup = 16;              // mirrors field 5
  // Gates inline image bytes. Cursor only honours SelectedImage.data /
  // ConversationHistoryImageContent when this is true. REQUIRED for images.
  bool client_supports_inline_images = 19;
}

message RequestedModel {
  string model_id = 1;
  // Repeated key/value params. Roder sends:
  //   thinking=true, context=300k, effort=high, fast=false
  // effort=high is required for full agentic thoroughness.
  repeated ModelParam params = 3;
}

message ModelParam {
  string key = 1;
  string value = 2;
}

message ConversationAction {
  UserMessageAction user_message_action = 1;
}

message UserMessageAction {
  UserMessage user_message = 1;
  ConversationHistory conversation_history = 7;  // omitted when empty
}

// AgentMode enum (UserMessage.mode). AGENT(1) enables the tool loop; ASK(2) is
// read-only ("Ask mode") and refuses edits.
enum AgentMode {
  AGENT_MODE_UNSPECIFIED = 0;
  AGENT_MODE_AGENT = 1;
  AGENT_MODE_ASK = 2;
  AGENT_MODE_PLAN = 3;
}

message UserMessage {
  string text = 1;
  string message_id = 2;
  SelectedContext selected_context = 3;          // attached images ride here
  AgentMode mode = 4;
}

// ===========================================================================
// Inline images on the live (current) user message
// ===========================================================================

message SelectedContext {
  repeated SelectedImage selected_images = 1;
  // Other selected_* context (files, symbols, docs) exists in the bundle but is
  // not encoded by Roder.
}

message SelectedImage {
  string uuid = 2;
  string path = 3;
  ImageDimension dimension = 4;
  string mime_type = 7;
  // oneof source { bytes data = 8; ... }
  // Inline raw image bytes. Roder sends only `data` (+ mime_type); `data`
  // requires AgentRunRequest.client_supports_inline_images = true.
  bytes data = 8;
}

message ImageDimension {
  uint32 width = 1;   // not sent by Roder
  uint32 height = 2;  // not sent by Roder
}

// ===========================================================================
// Conversation history (prior turns)
// ===========================================================================

message ConversationHistory {
  repeated ConversationHistoryMessage messages = 1;
}

message ConversationHistoryMessage {
  // Exactly one of:
  ConversationHistoryUserMessage user = 1;
  ConversationHistoryAssistantMessage assistant = 2;
  ConversationHistoryToolMessage tool = 3;
}

message ConversationHistoryUserMessage {
  repeated ConversationHistoryUserContent content = 1;
}

message ConversationHistoryUserContent {
  // oneof content:
  ConversationHistoryTextContent text = 1;
  ConversationHistoryImageContent image = 2;   // history images ride here
}

message ConversationHistoryTextContent {
  string text = 1;
}

// History images carry data as a STRING (base64 / data-URL), unlike the live
// SelectedImage which uses raw `bytes`.
message ConversationHistoryImageContent {
  string data = 1;
  string mime_type = 2;
}

message ConversationHistoryAssistantMessage {
  repeated ConversationHistoryAssistantContent content = 1;
}

message ConversationHistoryAssistantContent {
  // oneof content:
  ConversationHistoryTextContent text = 1;
  ConversationHistoryToolCall tool_call = 4;
}

message ConversationHistoryToolCall {
  string id = 1;
  string name = 2;
  string args_json = 3;
}

message ConversationHistoryToolMessage {
  string tool_call_id = 1;
  string tool_name = 2;
  repeated ToolResultContent content = 3;
  bool is_error = 4;
}

message ToolResultContent {
  string text = 1;
}

// ===========================================================================
// MCP / client-advertised tools
// ===========================================================================
//
// Recovered from the Cursor app bundle protobuf-es schema (cursor-agent-worker
// /dist/main.js). AgentRunRequest.mcp_tools (field 4) carries the tools the
// client advertises to the model. The model's call comes back in
// agent.v1.ToolCall.mcp_tool_call (field 15) as an McpToolCall.
//
// NOTE: `input_schema` and the per-call `args` values are google.protobuf.Value
// (JSON encoded as protobuf Value/Struct), NOT JSON strings.

// AgentRunRequest.mcp_tools (4).
message McpTools {
  repeated McpToolDefinition mcp_tools = 1;
}

// One advertised tool. Roder maps a ToolSpec -> this.
message McpToolDefinition {
  string name = 1;                               // tool name the model calls
  string description = 2;
  google.protobuf.Value input_schema = 3;        // JSON Schema as a Value
  string provider_identifier = 4;                // MCP provider/server id
  string tool_name = 5;                          // underlying server tool name
}

// Lighter descriptor variant (used in McpDescriptor.tools listings).
message McpToolDescriptor {
  string tool_name = 1;
  string definition_path = 2;
  string description = 3;
  google.protobuf.Value input_schema = 4;
}

// A configured MCP server and the tools it exposes (server-side listing).
message McpDescriptor {
  string server_name = 1;
  string server_identifier = 2;
  string folder_path = 3;
  string server_use_instructions = 4;
  repeated McpToolDescriptor tools = 5;
  string plugin = 7;
  string marketplace = 8;
  string plugin_db_id = 9;
  string marketplace_id = 10;
}

// AgentRunRequest.mcp_file_system_options (6). THIS is the MCP *registration*
// entry point (not mcp_tools/field 4, which a Cursor model ignores). The Cursor
// agent worker is a filesystem-based MCP host: each McpDescriptor names a server
// (`server_identifier` == the `provider_identifier` the model's McpArgs must
// reference) and lists its tools as McpToolDescriptors read from `definition_path`
// files under `folder_path`. Registering a server here is what makes its tools
// appear in the model's tool surface (surfaced via the get_mcp_tools meta-tool
// and invoked via mcp_tool_call). RESOLVED (bundle analysis): the resulting
// `mcp_tool_call` is NOT dispatched back to the client — it is executed by
// Cursor's backend against a server registered via the separate aiserver.v1
// MCP-config RPCs (SetMcpConfig/GetMcpConfig/GetAvailableMcpServers/…). The
// schema makes this conclusive: MCP *resource/state* ops have client-exec types
// (ReadMcpResourceExecArgs/Result, ListMcpResourcesExecArgs/Result,
// McpStateExecArgs/Result) but MCP *tool invocation* (McpArgs/McpToolResult) has
// NO *Exec* type. So an AgentService client (Roder) cannot service mcp_tool_call
// in-process; native exec tools (read/write/shell/grep/glob) remain the only way
// Roder's executor reaches a Cursor model. See docs/roder-cursor-provider.md.
message McpFileSystemOptions {
  bool enabled = 1;
  string workspace_project_dir = 2;
  repeated McpDescriptor mcp_descriptors = 3;
}

// MCP meta-tool: the model lazily discovers tools by calling get_mcp_tools
// (ToolCall field 44) -> GetMcpToolsArgs, and Cursor returns GetMcpToolsSuccess.
message GetMcpToolsArgs {
  string server = 1;
  string tool_name = 2;
  string pattern = 3;
  string tool_call_id = 4;
}

// agent.v1.ToolCall tool oneof field numbers (the interaction-update display +
// client-exec dispatch). The full oneof has 62 server-defined slots: a Cursor
// model's tool surface is fixed by the server and cannot be extended by the
// client — only the MCP slots below bridge to external/MCP tools. Native tools
// also execute via the exec channel; see
// docs/roder-cursor-agent-runtime-protocol.md.
//   shell_tool_call            = 1
//   glob_tool_call             = 4
//   grep_tool_call             = 5
//   read_tool_call             = 8
//   edit_tool_call             = 12
//   mcp_tool_call              = 15   <- McpToolCall (invoke a registered MCP tool)
//   list_mcp_resources_tool_call = 20
//   read_mcp_resource_tool_call  = 21
//   mcp_auth_tool_call         = 41
//   get_mcp_tools_tool_call    = 44   <- GetMcpToolsArgs (discover MCP tools)

message McpToolCall {
  McpArgs args = 1;
  McpToolResult result = 2;
}

// The model's MCP tool invocation. `args` is the tool input as a map of
// string -> google.protobuf.Value.
message McpArgs {
  string name = 1;
  map<string, google.protobuf.Value> args = 2;
  string tool_call_id = 3;
  string provider_identifier = 4;
  string tool_name = 5;
  // SmartModeApproval smart_mode_approval = 6;
  bool smart_mode_approval_only = 7;
  bool skip_approval = 8;
}

message McpToolResult {
  oneof result {
    McpSuccess success = 1;
    McpToolError error = 2;
    McpRejected rejected = 3;
    McpPermissionDenied permission_denied = 4;
  }
}

message McpSuccess {
  repeated McpToolResultContentItem content = 1;
  bool is_error = 2;
  google.protobuf.Value structured_content = 3;
}

message McpToolError {
  string error = 1;
  string read_tool_def_reminder = 2;
}

message McpRejected {}
message McpPermissionDenied {}

message McpToolResultContentItem {
  oneof content {
    McpTextContent text = 1;
    McpImageContent image = 2;
  }
}

message McpTextContent {
  string text = 1;
  // OutputLocation output_location = 2;
}

// Inline image result content (raw bytes).
message McpImageContent {
  bytes data = 1;
  string mime_type = 2;
}
