diff --git a/apps/docs/content/docs/en/tools/cursor.mdx b/apps/docs/content/docs/en/tools/cursor.mdx index 09bcd4644f4..4e6f165c014 100644 --- a/apps/docs/content/docs/en/tools/cursor.mdx +++ b/apps/docs/content/docs/en/tools/cursor.mdx @@ -45,6 +45,7 @@ List all cloud agents for the authenticated user with optional pagination. Retur | `apiKey` | string | Yes | Cursor API key | | `limit` | number | No | Number of agents to return \(default: 20, max: 100\) | | `cursor` | string | No | Pagination cursor from previous response | +| `prUrl` | string | No | Filter agents by pull request URL | #### Output @@ -173,4 +174,41 @@ Permanently delete a cloud agent. Returns API-aligned fields only. | --------- | ---- | ----------- | | `id` | string | Agent ID | +### `cursor_list_artifacts` + +List generated artifact files for a cloud agent. Returns API-aligned fields only. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Cursor API key | +| `agentId` | string | Yes | Unique identifier for the cloud agent \(e.g., bc_abc123\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `artifacts` | array | List of artifact files | +| ↳ `path` | string | Artifact file path | +| ↳ `size` | number | File size in bytes | + +### `cursor_download_artifact` + +Download a generated artifact file from a cloud agent. Returns the file for execution storage. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Cursor API key | +| `agentId` | string | Yes | Unique identifier for the cloud agent \(e.g., bc_abc123\) | +| `path` | string | Yes | Absolute path of the artifact to download \(e.g., /src/index.ts\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `file` | file | Downloaded artifact file stored in execution files | + diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 32881c28d13..9db82b6d349 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -2327,9 +2327,17 @@ { "name": "Delete Agent", "description": "Permanently delete a cloud agent. This action cannot be undone." + }, + { + "name": "List Artifacts", + "description": "List generated artifact files for a cloud agent." + }, + { + "name": "Download Artifact", + "description": "Download a generated artifact file from a cloud agent." } ], - "operationCount": 7, + "operationCount": 9, "triggers": [], "triggerCount": 0, "authType": "api-key", diff --git a/apps/sim/app/api/tools/cursor/download-artifact/route.ts b/apps/sim/app/api/tools/cursor/download-artifact/route.ts new file mode 100644 index 00000000000..bc185d1d86a --- /dev/null +++ b/apps/sim/app/api/tools/cursor/download-artifact/route.ts @@ -0,0 +1,146 @@ +import { createLogger } from '@sim/logger' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { + secureFetchWithPinnedIP, + validateUrlWithDNS, +} from '@/lib/core/security/input-validation.server' +import { generateRequestId } from '@/lib/core/utils/request' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('CursorDownloadArtifactAPI') + +const DownloadArtifactSchema = z.object({ + apiKey: z.string().min(1, 'API key is required'), + agentId: z.string().min(1, 'Agent ID is required'), + path: z.string().min(1, 'Artifact path is required'), +}) + +export async function POST(request: NextRequest) { + const requestId = generateRequestId() + + try { + const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) + + if (!authResult.success) { + logger.warn( + `[${requestId}] Unauthorized Cursor download artifact attempt: ${authResult.error}` + ) + return NextResponse.json( + { success: false, error: authResult.error || 'Authentication required' }, + { status: 401 } + ) + } + + logger.info( + `[${requestId}] Authenticated Cursor download artifact request via ${authResult.authType}`, + { + userId: authResult.userId, + } + ) + + const body = await request.json() + const { apiKey, agentId, path } = DownloadArtifactSchema.parse(body) + + const authHeader = `Basic ${Buffer.from(`${apiKey}:`).toString('base64')}` + + logger.info(`[${requestId}] Requesting presigned URL for artifact`, { agentId, path }) + + const artifactResponse = await fetch( + `https://api.cursor.com/v0/agents/${encodeURIComponent(agentId)}/artifacts/download?path=${encodeURIComponent(path)}`, + { + method: 'GET', + headers: { + Authorization: authHeader, + }, + } + ) + + if (!artifactResponse.ok) { + const errorText = await artifactResponse.text().catch(() => '') + logger.error(`[${requestId}] Failed to get artifact presigned URL`, { + status: artifactResponse.status, + error: errorText, + }) + return NextResponse.json( + { + success: false, + error: errorText || `Failed to get artifact URL (${artifactResponse.status})`, + }, + { status: artifactResponse.status } + ) + } + + const artifactData = await artifactResponse.json() + const downloadUrl = artifactData.url || artifactData.downloadUrl || artifactData.presignedUrl + + if (!downloadUrl) { + logger.error(`[${requestId}] No download URL in artifact response`, { artifactData }) + return NextResponse.json( + { success: false, error: 'No download URL returned for artifact' }, + { status: 400 } + ) + } + + const urlValidation = await validateUrlWithDNS(downloadUrl, 'downloadUrl') + if (!urlValidation.isValid) { + return NextResponse.json({ success: false, error: urlValidation.error }, { status: 400 }) + } + + logger.info(`[${requestId}] Downloading artifact from presigned URL`, { agentId, path }) + + const downloadResponse = await secureFetchWithPinnedIP( + downloadUrl, + urlValidation.resolvedIP!, + {} + ) + + if (!downloadResponse.ok) { + logger.error(`[${requestId}] Failed to download artifact content`, { + status: downloadResponse.status, + statusText: downloadResponse.statusText, + }) + return NextResponse.json( + { + success: false, + error: `Failed to download artifact content (${downloadResponse.status}: ${downloadResponse.statusText})`, + }, + { status: downloadResponse.status } + ) + } + + const contentType = downloadResponse.headers.get('content-type') || 'application/octet-stream' + const arrayBuffer = await downloadResponse.arrayBuffer() + const fileBuffer = Buffer.from(arrayBuffer) + + const fileName = path.split('/').pop() || 'artifact' + + logger.info(`[${requestId}] Artifact downloaded successfully`, { + agentId, + path, + name: fileName, + size: fileBuffer.length, + mimeType: contentType, + }) + + return NextResponse.json({ + success: true, + output: { + file: { + name: fileName, + mimeType: contentType, + data: fileBuffer.toString('base64'), + size: fileBuffer.length, + }, + }, + }) + } catch (error) { + logger.error(`[${requestId}] Error downloading Cursor artifact:`, error) + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }, + { status: 500 } + ) + } +} diff --git a/apps/sim/blocks/blocks/cursor.ts b/apps/sim/blocks/blocks/cursor.ts index 60ceec13e32..b6ef6a52411 100644 --- a/apps/sim/blocks/blocks/cursor.ts +++ b/apps/sim/blocks/blocks/cursor.ts @@ -31,6 +31,8 @@ export const CursorBlock: BlockConfig = { { label: 'List Agents', id: 'cursor_list_agents' }, { label: 'Stop Agent', id: 'cursor_stop_agent' }, { label: 'Delete Agent', id: 'cursor_delete_agent' }, + { label: 'List Artifacts', id: 'cursor_list_artifacts' }, + { label: 'Download Artifact', id: 'cursor_download_artifact' }, ], value: () => 'cursor_launch_agent', }, @@ -48,6 +50,7 @@ export const CursorBlock: BlockConfig = { type: 'short-input', placeholder: 'main (optional)', condition: { field: 'operation', value: 'cursor_launch_agent' }, + mode: 'advanced', }, { id: 'promptText', @@ -57,12 +60,21 @@ export const CursorBlock: BlockConfig = { condition: { field: 'operation', value: 'cursor_launch_agent' }, required: true, }, + { + id: 'promptImages', + title: 'Prompt Images', + type: 'long-input', + placeholder: '[{"data": "base64...", "dimension": {"width": 1024, "height": 768}}]', + condition: { field: 'operation', value: ['cursor_launch_agent', 'cursor_add_followup'] }, + mode: 'advanced', + }, { id: 'model', title: 'Model', type: 'short-input', placeholder: 'Auto-selection by default', condition: { field: 'operation', value: 'cursor_launch_agent' }, + mode: 'advanced', }, { id: 'branchName', @@ -70,6 +82,7 @@ export const CursorBlock: BlockConfig = { type: 'short-input', placeholder: 'Custom branch name (optional)', condition: { field: 'operation', value: 'cursor_launch_agent' }, + mode: 'advanced', }, { id: 'autoCreatePr', @@ -82,12 +95,14 @@ export const CursorBlock: BlockConfig = { title: 'Open as Cursor GitHub App', type: 'switch', condition: { field: 'operation', value: 'cursor_launch_agent' }, + mode: 'advanced', }, { id: 'skipReviewerRequest', title: 'Skip Reviewer Request', type: 'switch', condition: { field: 'operation', value: 'cursor_launch_agent' }, + mode: 'advanced', }, { id: 'agentId', @@ -102,10 +117,20 @@ export const CursorBlock: BlockConfig = { 'cursor_add_followup', 'cursor_stop_agent', 'cursor_delete_agent', + 'cursor_list_artifacts', + 'cursor_download_artifact', ], }, required: true, }, + { + id: 'path', + title: 'Artifact Path', + type: 'short-input', + placeholder: '/opt/cursor/artifacts/screenshot.png', + condition: { field: 'operation', value: 'cursor_download_artifact' }, + required: true, + }, { id: 'followupPromptText', title: 'Follow-up Prompt', @@ -114,12 +139,21 @@ export const CursorBlock: BlockConfig = { condition: { field: 'operation', value: 'cursor_add_followup' }, required: true, }, + { + id: 'prUrl', + title: 'PR URL Filter', + type: 'short-input', + placeholder: 'Filter by pull request URL (optional)', + condition: { field: 'operation', value: 'cursor_list_agents' }, + mode: 'advanced', + }, { id: 'limit', title: 'Limit', type: 'short-input', placeholder: '20 (default, max 100)', condition: { field: 'operation', value: 'cursor_list_agents' }, + mode: 'advanced', }, { id: 'cursor', @@ -127,6 +161,7 @@ export const CursorBlock: BlockConfig = { type: 'short-input', placeholder: 'Cursor from previous response', condition: { field: 'operation', value: 'cursor_list_agents' }, + mode: 'advanced', }, { id: 'apiKey', @@ -146,6 +181,8 @@ export const CursorBlock: BlockConfig = { 'cursor_add_followup', 'cursor_stop_agent', 'cursor_delete_agent', + 'cursor_list_artifacts', + 'cursor_download_artifact', ], config: { tool: (params) => params.operation || 'cursor_launch_agent', @@ -157,15 +194,20 @@ export const CursorBlock: BlockConfig = { ref: { type: 'string', description: 'Branch, tag, or commit reference' }, promptText: { type: 'string', description: 'Instruction text for the agent' }, followupPromptText: { type: 'string', description: 'Follow-up instruction text for the agent' }, - promptImages: { type: 'string', description: 'JSON array of image objects' }, + promptImages: { + type: 'string', + description: 'JSON array of image objects with base64 data and dimensions', + }, model: { type: 'string', description: 'Model to use (empty for auto-selection)' }, branchName: { type: 'string', description: 'Custom branch name' }, autoCreatePr: { type: 'boolean', description: 'Auto-create PR when done' }, openAsCursorGithubApp: { type: 'boolean', description: 'Open PR as Cursor GitHub App' }, skipReviewerRequest: { type: 'boolean', description: 'Skip reviewer request' }, agentId: { type: 'string', description: 'Agent identifier' }, + prUrl: { type: 'string', description: 'Filter agents by pull request URL' }, limit: { type: 'number', description: 'Number of results to return' }, cursor: { type: 'string', description: 'Pagination cursor' }, + path: { type: 'string', description: 'Absolute path of the artifact to download' }, apiKey: { type: 'string', description: 'Cursor API key' }, }, outputs: { @@ -192,6 +234,8 @@ export const CursorV2Block: BlockConfig = { 'cursor_add_followup_v2', 'cursor_stop_agent_v2', 'cursor_delete_agent_v2', + 'cursor_list_artifacts_v2', + 'cursor_download_artifact_v2', ], config: { tool: createVersionedToolSelector({ @@ -213,5 +257,7 @@ export const CursorV2Block: BlockConfig = { agents: { type: 'json', description: 'Array of agent objects (list operation)' }, nextCursor: { type: 'string', description: 'Pagination cursor (list operation)' }, messages: { type: 'json', description: 'Conversation messages (get conversation operation)' }, + artifacts: { type: 'json', description: 'List of artifact files (list artifacts operation)' }, + file: { type: 'file', description: 'Downloaded artifact file (download artifact operation)' }, }, } diff --git a/apps/sim/tools/cursor/add_followup.ts b/apps/sim/tools/cursor/add_followup.ts index 5e161fc27f9..8dcf46c3b20 100644 --- a/apps/sim/tools/cursor/add_followup.ts +++ b/apps/sim/tools/cursor/add_followup.ts @@ -30,7 +30,7 @@ const addFollowupBase = { }, request: { url: (params: AddFollowupParams) => - `https://api.cursor.com/v0/agents/${params.agentId}/followup`, + `https://api.cursor.com/v0/agents/${params.agentId.trim()}/followup`, method: 'POST', headers: (params: AddFollowupParams) => ({ 'Content-Type': 'application/json', diff --git a/apps/sim/tools/cursor/delete_agent.ts b/apps/sim/tools/cursor/delete_agent.ts index 2acf9ea0dec..a3b8e51a45d 100644 --- a/apps/sim/tools/cursor/delete_agent.ts +++ b/apps/sim/tools/cursor/delete_agent.ts @@ -17,7 +17,7 @@ const deleteAgentBase = { }, }, request: { - url: (params: DeleteAgentParams) => `https://api.cursor.com/v0/agents/${params.agentId}`, + url: (params: DeleteAgentParams) => `https://api.cursor.com/v0/agents/${params.agentId.trim()}`, method: 'DELETE', headers: (params: DeleteAgentParams) => ({ Authorization: `Basic ${Buffer.from(`${params.apiKey}:`).toString('base64')}`, diff --git a/apps/sim/tools/cursor/download_artifact.ts b/apps/sim/tools/cursor/download_artifact.ts new file mode 100644 index 00000000000..5b891688410 --- /dev/null +++ b/apps/sim/tools/cursor/download_artifact.ts @@ -0,0 +1,113 @@ +import type { + DownloadArtifactParams, + DownloadArtifactResponse, + DownloadArtifactV2Response, +} from '@/tools/cursor/types' +import type { ToolConfig } from '@/tools/types' + +const downloadArtifactBase = { + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Cursor API key', + }, + agentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier for the cloud agent (e.g., bc_abc123)', + }, + path: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Absolute path of the artifact to download (e.g., /src/index.ts)', + }, + }, + request: { + url: '/api/tools/cursor/download-artifact', + method: 'POST', + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params: DownloadArtifactParams) => ({ + apiKey: params.apiKey, + agentId: params.agentId?.trim(), + path: params.path?.trim(), + }), + }, +} satisfies Pick, 'params' | 'request'> + +export const downloadArtifactTool: ToolConfig = { + id: 'cursor_download_artifact', + name: 'Cursor Download Artifact', + description: 'Download a generated artifact file from a cloud agent.', + version: '1.0.0', + + ...downloadArtifactBase, + + transformResponse: async (response) => { + const data = await response.json() + + if (!data.success) { + throw new Error(data.error || 'Failed to download artifact') + } + + return { + success: true, + output: { + content: `Downloaded artifact: ${data.output.file.name}`, + metadata: data.output.file, + }, + } + }, + + outputs: { + content: { type: 'string', description: 'Human-readable download result' }, + metadata: { + type: 'object', + description: 'Downloaded file metadata', + properties: { + name: { type: 'string', description: 'File name' }, + mimeType: { type: 'string', description: 'MIME type' }, + size: { type: 'number', description: 'File size in bytes' }, + }, + }, + }, +} + +export const downloadArtifactV2Tool: ToolConfig< + DownloadArtifactParams, + DownloadArtifactV2Response +> = { + ...downloadArtifactBase, + id: 'cursor_download_artifact_v2', + name: 'Cursor Download Artifact', + description: + 'Download a generated artifact file from a cloud agent. Returns the file for execution storage.', + version: '2.0.0', + + transformResponse: async (response) => { + const data = await response.json() + + if (!data.success) { + throw new Error(data.error || 'Failed to download artifact') + } + + return { + success: true, + output: { + file: data.output.file, + }, + } + }, + + outputs: { + file: { + type: 'file', + description: 'Downloaded artifact file stored in execution files', + }, + }, +} diff --git a/apps/sim/tools/cursor/get_agent.ts b/apps/sim/tools/cursor/get_agent.ts index 920dec4a29f..9ac829c26fd 100644 --- a/apps/sim/tools/cursor/get_agent.ts +++ b/apps/sim/tools/cursor/get_agent.ts @@ -17,7 +17,7 @@ const getAgentBase = { }, }, request: { - url: (params: GetAgentParams) => `https://api.cursor.com/v0/agents/${params.agentId}`, + url: (params: GetAgentParams) => `https://api.cursor.com/v0/agents/${params.agentId.trim()}`, method: 'GET', headers: (params: GetAgentParams) => ({ Authorization: `Basic ${Buffer.from(`${params.apiKey}:`).toString('base64')}`, diff --git a/apps/sim/tools/cursor/get_conversation.ts b/apps/sim/tools/cursor/get_conversation.ts index 32752ea7b31..c271e857823 100644 --- a/apps/sim/tools/cursor/get_conversation.ts +++ b/apps/sim/tools/cursor/get_conversation.ts @@ -18,7 +18,7 @@ const getConversationBase = { }, request: { url: (params: GetConversationParams) => - `https://api.cursor.com/v0/agents/${params.agentId}/conversation`, + `https://api.cursor.com/v0/agents/${params.agentId.trim()}/conversation`, method: 'GET', headers: (params: GetConversationParams) => ({ Authorization: `Basic ${Buffer.from(`${params.apiKey}:`).toString('base64')}`, diff --git a/apps/sim/tools/cursor/index.ts b/apps/sim/tools/cursor/index.ts index 2ec9cebfd15..80573009eec 100644 --- a/apps/sim/tools/cursor/index.ts +++ b/apps/sim/tools/cursor/index.ts @@ -1,9 +1,11 @@ import { addFollowupTool, addFollowupV2Tool } from '@/tools/cursor/add_followup' import { deleteAgentTool, deleteAgentV2Tool } from '@/tools/cursor/delete_agent' +import { downloadArtifactTool, downloadArtifactV2Tool } from '@/tools/cursor/download_artifact' import { getAgentTool, getAgentV2Tool } from '@/tools/cursor/get_agent' import { getConversationTool, getConversationV2Tool } from '@/tools/cursor/get_conversation' import { launchAgentTool, launchAgentV2Tool } from '@/tools/cursor/launch_agent' import { listAgentsTool, listAgentsV2Tool } from '@/tools/cursor/list_agents' +import { listArtifactsTool, listArtifactsV2Tool } from '@/tools/cursor/list_artifacts' import { stopAgentTool, stopAgentV2Tool } from '@/tools/cursor/stop_agent' export const cursorListAgentsTool = listAgentsTool @@ -13,6 +15,8 @@ export const cursorLaunchAgentTool = launchAgentTool export const cursorAddFollowupTool = addFollowupTool export const cursorStopAgentTool = stopAgentTool export const cursorDeleteAgentTool = deleteAgentTool +export const cursorDownloadArtifactTool = downloadArtifactTool +export const cursorListArtifactsTool = listArtifactsTool export const cursorListAgentsV2Tool = listAgentsV2Tool export const cursorGetAgentV2Tool = getAgentV2Tool @@ -21,3 +25,5 @@ export const cursorLaunchAgentV2Tool = launchAgentV2Tool export const cursorAddFollowupV2Tool = addFollowupV2Tool export const cursorStopAgentV2Tool = stopAgentV2Tool export const cursorDeleteAgentV2Tool = deleteAgentV2Tool +export const cursorDownloadArtifactV2Tool = downloadArtifactV2Tool +export const cursorListArtifactsV2Tool = listArtifactsV2Tool diff --git a/apps/sim/tools/cursor/list_agents.ts b/apps/sim/tools/cursor/list_agents.ts index 9c52b754afa..6caa4686f92 100644 --- a/apps/sim/tools/cursor/list_agents.ts +++ b/apps/sim/tools/cursor/list_agents.ts @@ -21,12 +21,19 @@ const listAgentsBase = { visibility: 'user-or-llm', description: 'Pagination cursor from previous response', }, + prUrl: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter agents by pull request URL', + }, }, request: { url: (params: ListAgentsParams) => { const url = new URL('https://api.cursor.com/v0/agents') if (params.limit) url.searchParams.set('limit', String(params.limit)) if (params.cursor) url.searchParams.set('cursor', params.cursor) + if (params.prUrl) url.searchParams.set('prUrl', params.prUrl) return url.toString() }, method: 'GET', @@ -53,7 +60,7 @@ export const listAgentsTool: ToolConfig = content: `Found ${data.agents.length} agents`, metadata: { agents: data.agents, - nextCursor: data.nextCursor, + nextCursor: data.nextCursor ?? null, }, }, } diff --git a/apps/sim/tools/cursor/list_artifacts.ts b/apps/sim/tools/cursor/list_artifacts.ts new file mode 100644 index 00000000000..99580f7d6a7 --- /dev/null +++ b/apps/sim/tools/cursor/list_artifacts.ts @@ -0,0 +1,113 @@ +import type { ListArtifactsParams, ListArtifactsResponse } from '@/tools/cursor/types' +import type { ToolConfig } from '@/tools/types' + +const listArtifactsBase = { + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Cursor API key', + }, + agentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier for the cloud agent (e.g., bc_abc123)', + }, + }, + request: { + url: (params: ListArtifactsParams) => + `https://api.cursor.com/v0/agents/${params.agentId.trim()}/artifacts`, + method: 'GET', + headers: (params: ListArtifactsParams) => ({ + Authorization: `Basic ${Buffer.from(`${params.apiKey}:`).toString('base64')}`, + }), + }, +} satisfies Pick, 'params' | 'request'> + +export const listArtifactsTool: ToolConfig = { + id: 'cursor_list_artifacts', + name: 'Cursor List Artifacts', + description: 'List generated artifact files for a cloud agent.', + version: '1.0.0', + + ...listArtifactsBase, + + transformResponse: async (response) => { + const data = await response.json() + const artifacts = data.artifacts ?? [] + + return { + success: true, + output: { + content: `Found ${artifacts.length} artifact(s)`, + metadata: { + artifacts, + }, + }, + } + }, + + outputs: { + content: { type: 'string', description: 'Human-readable artifact count' }, + metadata: { + type: 'object', + description: 'Artifacts metadata', + properties: { + artifacts: { + type: 'array', + description: 'List of artifacts', + items: { + type: 'object', + properties: { + path: { type: 'string', description: 'Artifact file path' }, + size: { type: 'number', description: 'File size in bytes', optional: true }, + }, + }, + }, + }, + }, + }, +} + +interface ListArtifactsV2Response { + success: boolean + output: { + artifacts: Array<{ path: string; size?: number }> + } +} + +export const listArtifactsV2Tool: ToolConfig = { + ...listArtifactsBase, + id: 'cursor_list_artifacts_v2', + name: 'Cursor List Artifacts', + description: 'List generated artifact files for a cloud agent. Returns API-aligned fields only.', + version: '2.0.0', + + transformResponse: async (response) => { + const data = await response.json() + const artifacts = data.artifacts ?? [] + + return { + success: true, + output: { + artifacts: Array.isArray(artifacts) ? artifacts : [], + }, + } + }, + + outputs: { + artifacts: { + type: 'array', + description: 'List of artifact files', + items: { + type: 'object', + properties: { + path: { type: 'string', description: 'Artifact file path' }, + size: { type: 'number', description: 'File size in bytes', optional: true }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/cursor/stop_agent.ts b/apps/sim/tools/cursor/stop_agent.ts index 728ca9af608..04373c16644 100644 --- a/apps/sim/tools/cursor/stop_agent.ts +++ b/apps/sim/tools/cursor/stop_agent.ts @@ -17,7 +17,8 @@ const stopAgentBase = { }, }, request: { - url: (params: StopAgentParams) => `https://api.cursor.com/v0/agents/${params.agentId}/stop`, + url: (params: StopAgentParams) => + `https://api.cursor.com/v0/agents/${params.agentId.trim()}/stop`, method: 'POST', headers: (params: StopAgentParams) => ({ Authorization: `Basic ${Buffer.from(`${params.apiKey}:`).toString('base64')}`, diff --git a/apps/sim/tools/cursor/types.ts b/apps/sim/tools/cursor/types.ts index 4ec843ce280..3cce5f362ca 100644 --- a/apps/sim/tools/cursor/types.ts +++ b/apps/sim/tools/cursor/types.ts @@ -7,6 +7,7 @@ export interface BaseCursorParams { export interface ListAgentsParams extends BaseCursorParams { limit?: number cursor?: string + prUrl?: string } export interface GetAgentParams extends BaseCursorParams { @@ -60,7 +61,7 @@ interface AgentTarget { interface AgentMetadata { id: string name: string - status: 'RUNNING' | 'FINISHED' | 'STOPPED' | 'FAILED' + status: 'CREATING' | 'RUNNING' | 'FINISHED' | 'STOPPED' | 'FAILED' source: AgentSource target: AgentTarget summary?: string @@ -174,6 +175,47 @@ export interface ListRepositoriesResponse extends ToolResponse { } } +export interface ListArtifactsParams extends BaseCursorParams { + agentId: string +} + +export interface ArtifactMetadata { + path: string + size?: number +} + +export interface ListArtifactsResponse extends ToolResponse { + output: { + content: string + metadata: { + artifacts: ArtifactMetadata[] + } + } +} + +export interface DownloadArtifactParams extends BaseCursorParams { + agentId: string + path: string +} + +export interface DownloadArtifactResponse extends ToolResponse { + output: { + content: string + metadata: Record + } +} + +export interface DownloadArtifactV2Response extends ToolResponse { + output: { + file: { + name: string + mimeType: string + data: string + size: number + } + } +} + export type CursorResponse = | ListAgentsResponse | GetAgentResponse @@ -185,3 +227,6 @@ export type CursorResponse = | GetApiKeyInfoResponse | ListModelsResponse | ListRepositoriesResponse + | ListArtifactsResponse + | DownloadArtifactResponse + | DownloadArtifactV2Response diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index d5065039edd..b39ab298a63 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -346,6 +346,8 @@ import { cursorAddFollowupV2Tool, cursorDeleteAgentTool, cursorDeleteAgentV2Tool, + cursorDownloadArtifactTool, + cursorDownloadArtifactV2Tool, cursorGetAgentTool, cursorGetAgentV2Tool, cursorGetConversationTool, @@ -354,6 +356,8 @@ import { cursorLaunchAgentV2Tool, cursorListAgentsTool, cursorListAgentsV2Tool, + cursorListArtifactsTool, + cursorListArtifactsV2Tool, cursorStopAgentTool, cursorStopAgentV2Tool, } from '@/tools/cursor' @@ -4123,6 +4127,10 @@ export const tools: Record = { cursor_stop_agent_v2: cursorStopAgentV2Tool, cursor_delete_agent: cursorDeleteAgentTool, cursor_delete_agent_v2: cursorDeleteAgentV2Tool, + cursor_download_artifact: cursorDownloadArtifactTool, + cursor_download_artifact_v2: cursorDownloadArtifactV2Tool, + cursor_list_artifacts: cursorListArtifactsTool, + cursor_list_artifacts_v2: cursorListArtifactsV2Tool, trello_list_lists: trelloListListsTool, trello_list_cards: trelloListCardsTool, trello_create_card: trelloCreateCardTool,