Update n8n Skyvern.node.ts to include webhook_callback_url option in … (#2230)

This commit is contained in:
dd36
2025-04-30 11:10:14 -07:00
committed by GitHub
parent c069ebe6f9
commit 3af35bc428

View File

@@ -10,18 +10,18 @@ async function makeRequest(url: string, options: any = {}): Promise<any> {
const requestOptions = { const requestOptions = {
hostname: parsedUrl.hostname, hostname: parsedUrl.hostname,
path: parsedUrl.pathname + parsedUrl.search, path: parsedUrl.pathname + parsedUrl.search,
port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80), port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
method: options.method || 'GET', method: options.method || 'GET',
headers: options.headers || {}, headers: options.headers || {},
}; };
const req = transport.request(requestOptions, (res) => { const req = transport.request(requestOptions, (res) => {
let data = ''; let data = '';
res.on('data', (chunk) => { res.on('data', (chunk) => {
data += chunk; data += chunk;
}); });
res.on('end', () => { res.on('end', () => {
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
const response = { const response = {
@@ -47,15 +47,15 @@ async function makeRequest(url: string, options: any = {}): Promise<any> {
} }
}); });
}); });
req.on('error', (error) => { req.on('error', (error) => {
reject(error); reject(error);
}); });
if (options.body) { if (options.body) {
req.write(options.body); req.write(options.body);
} }
req.end(); req.end();
}); });
} }
@@ -83,7 +83,7 @@ export class Skyvern implements INodeType {
displayName: 'Resource', displayName: 'Resource',
name: 'resource', name: 'resource',
type: 'options', type: 'options',
noDataExpression: true, noDataExpression: true,
options: [ options: [
{ {
name: 'Task', name: 'Task',
@@ -93,7 +93,6 @@ export class Skyvern implements INodeType {
name: 'Workflow', name: 'Workflow',
value: 'workflow', value: 'workflow',
}, },
], ],
default: 'task', default: 'task',
}, },
@@ -135,18 +134,31 @@ export class Skyvern implements INodeType {
const taskOptions: IDataObject = this.getNodeParameter('taskOptions') as IDataObject; const taskOptions: IDataObject = this.getNodeParameter('taskOptions') as IDataObject;
if (taskOptions["engine"] !== "v1") return requestOptions; if (taskOptions["engine"] !== "v1") return requestOptions;
// trigger the generate task v1 logic
const credentials = await this.getCredentials('skyvernApi'); const credentials = await this.getCredentials('skyvernApi');
const userPrompt = this.getNodeParameter('userPrompt'); const userPrompt = this.getNodeParameter('userPrompt');
// *** capture optional webhook URL ***
let webhookUrl: string | undefined;
try {
webhookUrl = this.getNodeParameter('webhookUrl') as string;
} catch (e) {
webhookUrl = undefined;
}
const generateBody: IDataObject = {
prompt: userPrompt,
};
if (webhookUrl) {
generateBody['webhook_callback_url'] = webhookUrl; // include for TaskV1 generation
}
const response = await makeRequest(credentials['baseUrl'] + '/api/v1/generate/task', { const response = await makeRequest(credentials['baseUrl'] + '/api/v1/generate/task', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'x-api-key': credentials['apiKey'], 'x-api-key': credentials['apiKey'],
}, },
body: JSON.stringify({ body: JSON.stringify(generateBody),
prompt: userPrompt,
}),
}); });
if (!response.ok) { if (!response.ok) {
throw new Error('Request to generate Task V1 failed'); // eslint-disable-line throw new Error('Request to generate Task V1 failed'); // eslint-disable-line
@@ -159,7 +171,11 @@ export class Skyvern implements INodeType {
navigation_payload: data.navigation_payload, navigation_payload: data.navigation_payload,
data_extraction_goal: data.data_extraction_goal, data_extraction_goal: data.data_extraction_goal,
extracted_information_schema: data.extracted_information_schema, extracted_information_schema: data.extracted_information_schema,
}; } as IDataObject;
if (webhookUrl) {
(requestOptions.body as IDataObject)['webhook_callback_url'] = webhookUrl;
}
return requestOptions; return requestOptions;
}, },
], ],
@@ -209,6 +225,28 @@ export class Skyvern implements INodeType {
}, },
}, },
}, },
// *** New property: optional webhook URL for Task dispatch ***
{
displayName: 'Webhook Callback URL',
description: 'Optional URL that Skyvern will call when the task finishes',
name: 'webhookUrl',
type: 'string',
default: '',
placeholder: 'https://example.com/webhook',
displayOptions: {
show: {
resource: ['task'],
taskOperation: ['dispatch'],
},
},
routing: {
request: {
body: {
webhook_callback_url: '={{$value ? $value : undefined}}',
},
},
},
},
{ {
displayName: 'Task ID', displayName: 'Task ID',
description: 'The ID of the task', description: 'The ID of the task',
@@ -365,10 +403,32 @@ export class Skyvern implements INodeType {
}, },
}, },
}, },
// *** New property: optional webhook URL for Workflow dispatch ***
{
displayName: 'Webhook Callback URL',
description: 'Optional URL that Skyvern will call when the workflow run finishes',
name: 'webhookCallbackUrl',
type: 'string',
default: '',
placeholder: 'https://example.com/webhook',
displayOptions: {
show: {
resource: ['workflow'],
workflowOperation: ['dispatch'],
},
},
routing: {
request: {
body: {
webhook_callback_url: '={{$value ? $value : undefined}}',
},
},
},
},
], ],
version: 1, version: 1,
}; };
methods = { methods = {
loadOptions: { loadOptions: {
async getWorkflows(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> { async getWorkflows(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
@@ -398,7 +458,7 @@ export class Skyvern implements INodeType {
const workflowOperation = this.getCurrentNodeParameter('workflowOperation') as string; const workflowOperation = this.getCurrentNodeParameter('workflowOperation') as string;
if (workflowOperation !== 'dispatch') return { fields: [] }; if (workflowOperation !== 'dispatch') return { fields: [] };
const workflowId = this.getCurrentNodeParameter('workflowId') as string; const workflowId = this.getCurrentNodeParameter('workflowId') as string;
if (!workflowId) return { fields: [] }; if (!workflowId) return { fields: [] };
@@ -415,48 +475,49 @@ export class Skyvern implements INodeType {
const parameters: any[] = workflow.workflow_definition.parameters; const parameters: any[] = workflow.workflow_definition.parameters;
const fields: ResourceMapperField[] = await Promise.all( const fields: ResourceMapperField[] = await Promise.all(
parameters.filter((parameter: any) => parameter.parameter_type === 'workflow' || parameter.parameter_type === 'credential') parameters
.map(async (parameter: any) => { .filter((parameter: any) => parameter.parameter_type === 'workflow' || parameter.parameter_type === 'credential')
let options: INodePropertyOptions[] | undefined = undefined; .map(async (parameter: any) => {
let parameterType: FieldType | undefined = undefined; let options: INodePropertyOptions[] | undefined = undefined;
if (parameter.parameter_type === 'credential') { let parameterType: FieldType | undefined = undefined;
const response = await makeRequest(credentials['baseUrl'] + '/api/v1/credentials', { if (parameter.parameter_type === 'credential') {
headers: { const credResponse = await makeRequest(credentials['baseUrl'] + '/api/v1/credentials', {
'x-api-key': credentials['apiKey'], headers: {
}, 'x-api-key': credentials['apiKey'],
}); },
if (!response.ok) { });
throw new Error('Request to get credentials failed'); // eslint-disable-line if (!credResponse.ok) {
throw new Error('Request to get credentials failed');
}
const credData = await credResponse.json();
options = credData.map((credential: any) => ({
name: credential.name,
value: credential.credential_id,
}));
parameterType = 'options';
} else {
const parameter_type_map: Record<string, FieldType> = {
string: 'string',
integer: 'number',
float: 'number',
boolean: 'boolean',
json: 'json',
file_url: 'url',
}
parameterType = parameter_type_map[parameter.workflow_parameter_type];
} }
const data = await response.json();
options = data.map((credential: any) => ({
name: credential.name,
value: credential.credential_id,
}));
parameterType = 'options';
}else{
const parameter_type_map: Record<string, string> = {
'string': 'string',
'integer': 'number',
'float': 'number',
'boolean': 'boolean',
'json': 'json',
'file_url': 'url',
}
parameterType = parameter_type_map[parameter.workflow_parameter_type] as FieldType;
}
return { return {
id: parameter.key, id: parameter.key,
displayName: parameter.key, displayName: parameter.key,
defaultMatch: true, defaultMatch: true,
canBeUsedToMatch: false, canBeUsedToMatch: false,
required: parameter.default_value === undefined || parameter.default_value === null, required: parameter.default_value === undefined || parameter.default_value === null,
display: true, display: true,
type: parameterType, type: parameterType,
options: options, options: options,
}; };
}) })
); );