Run python nodes out of process, with modules of their own
User code no longer execs in the engine. A pool of persistent worker subprocesses speaks one JSON object per line; the controller installs a proxy as the node's function, so every execution path funnels through it and the pipeline is untouched. A crash costs one subprocess, a per-node timeout is a kill, and cancelling from the canvas is that same kill. The workers run a venv of the user's own on the data volume, filled from a pip manifest versioned beside the flows. Applying it retires the workers and rebuilds, so a package lands without restarting the engine. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017MeiWk3Yq12n2pTvnQWYvt
This commit is contained in:
@@ -27,6 +27,36 @@ export const AlertsConfigSchema = {
|
||||
description: 'The whole alerting setup, as stored and as the API sees it.'
|
||||
} as const;
|
||||
|
||||
export const ApplyRequestSchema = {
|
||||
properties: {
|
||||
requirements: {
|
||||
type: 'string',
|
||||
title: 'Requirements',
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
title: 'ApplyRequest',
|
||||
description: 'A pip manifest, one requirement per line.'
|
||||
} as const;
|
||||
|
||||
export const ApplyResultSchema = {
|
||||
properties: {
|
||||
ok: {
|
||||
type: 'boolean',
|
||||
title: 'Ok'
|
||||
},
|
||||
output: {
|
||||
type: 'string',
|
||||
title: 'Output',
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['ok'],
|
||||
title: 'ApplyResult'
|
||||
} as const;
|
||||
|
||||
export const Body_login_login_access_tokenSchema = {
|
||||
properties: {
|
||||
grant_type: {
|
||||
@@ -890,6 +920,58 @@ export const MessagesPublicSchema = {
|
||||
title: 'MessagesPublic'
|
||||
} as const;
|
||||
|
||||
export const ModulePackageSchema = {
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
title: 'Name'
|
||||
},
|
||||
version: {
|
||||
type: 'string',
|
||||
title: 'Version'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['name', 'version'],
|
||||
title: 'ModulePackage',
|
||||
description: 'One package installed in the venv node code runs on.'
|
||||
} as const;
|
||||
|
||||
export const ModulesInfoSchema = {
|
||||
properties: {
|
||||
python_version: {
|
||||
type: 'string',
|
||||
title: 'Python Version',
|
||||
default: ''
|
||||
},
|
||||
venv_path: {
|
||||
type: 'string',
|
||||
title: 'Venv Path',
|
||||
default: ''
|
||||
},
|
||||
requirements: {
|
||||
type: 'string',
|
||||
title: 'Requirements',
|
||||
default: ''
|
||||
},
|
||||
packages: {
|
||||
items: {
|
||||
'$ref': '#/components/schemas/ModulePackage'
|
||||
},
|
||||
type: 'array',
|
||||
title: 'Packages'
|
||||
},
|
||||
applied: {
|
||||
type: 'boolean',
|
||||
title: 'Applied',
|
||||
default: false
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
title: 'ModulesInfo',
|
||||
description: 'The venv node code imports from, and the manifest that describes it.'
|
||||
} as const;
|
||||
|
||||
export const NewPasswordSchema = {
|
||||
properties: {
|
||||
token: {
|
||||
@@ -960,6 +1042,19 @@ export const NodeDef_InputSchema = {
|
||||
}
|
||||
],
|
||||
title: 'Source Ref'
|
||||
},
|
||||
timeout: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'number',
|
||||
exclusiveMinimum: 0
|
||||
},
|
||||
{
|
||||
type: 'null'
|
||||
}
|
||||
],
|
||||
title: 'Timeout',
|
||||
description: "Seconds this node's code may run before it is stopped. Above 60 the engine may deliver its work again while it is still running."
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
@@ -1020,6 +1115,19 @@ export const NodeDef_OutputSchema = {
|
||||
}
|
||||
],
|
||||
title: 'Source Ref'
|
||||
},
|
||||
timeout: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'number',
|
||||
exclusiveMinimum: 0
|
||||
},
|
||||
{
|
||||
type: 'null'
|
||||
}
|
||||
],
|
||||
title: 'Timeout',
|
||||
description: "Seconds this node's code may run before it is stopped. Above 60 the engine may deliver its work again while it is still running."
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import type { CancelablePromise } from './core/CancelablePromise';
|
||||
import { OpenAPI } from './core/OpenAPI';
|
||||
import { request as __request } from './core/request';
|
||||
import type { AlertsReadAlertsConfigResponse, AlertsSaveAlertsConfigData, AlertsSaveAlertsConfigResponse, AlertsTestChannelData, AlertsTestChannelResponse, DashboardsReadDashboardsResponse, DashboardsReadDashboardData, DashboardsReadDashboardResponse, DashboardsCreateDashboardData, DashboardsCreateDashboardResponse, DashboardsSaveDashboardData, DashboardsSaveDashboardResponse, DashboardsDeleteDashboardData, DashboardsDeleteDashboardResponse, DashboardsRenameDashboardData, DashboardsRenameDashboardResponse, FlowsReadFlowsResponse, FlowsReadNodeTypesResponse, FlowsReadLibraryResponse, FlowsDeleteSharedNodeData, FlowsDeleteSharedNodeResponse, FlowsReadFlowData, FlowsReadFlowResponse, FlowsSaveFlowData, FlowsSaveFlowResponse, FlowsDeleteFlowData, FlowsDeleteFlowResponse, FlowsPublishFlowData, FlowsPublishFlowResponse, FlowsDiscardDraftData, FlowsDiscardDraftResponse, FlowsRenameFlowData, FlowsRenameFlowResponse, FlowsReadNodeSourceData, FlowsReadNodeSourceResponse, FlowsSaveNodeSourceData, FlowsSaveNodeSourceResponse, FlowsShareNodeData, FlowsShareNodeResponse, FlowsUnshareNodeData, FlowsUnshareNodeResponse, FlowsStartFlowData, FlowsStartFlowResponse, FlowsStopFlowData, FlowsStopFlowResponse, FlowsPauseFlowData, FlowsPauseFlowResponse, FlowsResumeFlowData, FlowsResumeFlowResponse, FlowsStepFlowData, FlowsStepFlowResponse, FlowsValidateFlowData, FlowsValidateFlowResponse, FlowsRunFlowData, FlowsRunFlowResponse, FlowsTriggerNodeData, FlowsTriggerNodeResponse, FlowsReadFlowStateData, FlowsReadFlowStateResponse, FlowsReadMessageHistoryData, FlowsReadMessageHistoryResponse, LoginLoginAccessTokenData, LoginLoginAccessTokenResponse, LoginTestTokenResponse, LoginRecoverPasswordData, LoginRecoverPasswordResponse, LoginResetPasswordData, LoginResetPasswordResponse, LoginRecoverPasswordHtmlContentData, LoginRecoverPasswordHtmlContentResponse, MessagesReadMessagesResponse, MessagesPublishMessageData, MessagesPublishMessageResponse, MessagesReadMessageHistoryData, MessagesReadMessageHistoryResponse, OauthRegisterClientData, OauthRegisterClientResponse, OauthAuthorizeValidateData, OauthAuthorizeValidateResponse, OauthAuthorizeData, OauthAuthorizeResponse, OauthTokenData, OauthTokenResponse, OauthReadClientsResponse, OauthRevokeClientData, OauthRevokeClientResponse, PrivateCreateUserData, PrivateCreateUserResponse, SecretsReadSecretsResponse, SecretsSaveSecretData, SecretsSaveSecretResponse, SecretsDeleteSecretData, SecretsDeleteSecretResponse, UsersReadUsersData, UsersReadUsersResponse, UsersCreateUserData, UsersCreateUserResponse, UsersReadUserMeResponse, UsersDeleteUserMeResponse, UsersUpdateUserMeData, UsersUpdateUserMeResponse, UsersUpdatePasswordMeData, UsersUpdatePasswordMeResponse, UsersRegisterUserData, UsersRegisterUserResponse, UsersReadUserByIdData, UsersReadUserByIdResponse, UsersUpdateUserData, UsersUpdateUserResponse, UsersDeleteUserData, UsersDeleteUserResponse, UtilsTestEmailData, UtilsTestEmailResponse, UtilsHealthCheckResponse, UtilsHealthResponse } from './types.gen';
|
||||
import type { AlertsReadAlertsConfigResponse, AlertsSaveAlertsConfigData, AlertsSaveAlertsConfigResponse, AlertsTestChannelData, AlertsTestChannelResponse, DashboardsReadDashboardsResponse, DashboardsReadDashboardData, DashboardsReadDashboardResponse, DashboardsCreateDashboardData, DashboardsCreateDashboardResponse, DashboardsSaveDashboardData, DashboardsSaveDashboardResponse, DashboardsDeleteDashboardData, DashboardsDeleteDashboardResponse, DashboardsRenameDashboardData, DashboardsRenameDashboardResponse, FlowsReadFlowsResponse, FlowsReadNodeTypesResponse, FlowsReadLibraryResponse, FlowsDeleteSharedNodeData, FlowsDeleteSharedNodeResponse, FlowsReadFlowData, FlowsReadFlowResponse, FlowsSaveFlowData, FlowsSaveFlowResponse, FlowsDeleteFlowData, FlowsDeleteFlowResponse, FlowsPublishFlowData, FlowsPublishFlowResponse, FlowsDiscardDraftData, FlowsDiscardDraftResponse, FlowsRenameFlowData, FlowsRenameFlowResponse, FlowsReadNodeSourceData, FlowsReadNodeSourceResponse, FlowsSaveNodeSourceData, FlowsSaveNodeSourceResponse, FlowsShareNodeData, FlowsShareNodeResponse, FlowsUnshareNodeData, FlowsUnshareNodeResponse, FlowsStartFlowData, FlowsStartFlowResponse, FlowsStopFlowData, FlowsStopFlowResponse, FlowsPauseFlowData, FlowsPauseFlowResponse, FlowsResumeFlowData, FlowsResumeFlowResponse, FlowsStepFlowData, FlowsStepFlowResponse, FlowsValidateFlowData, FlowsValidateFlowResponse, FlowsRunFlowData, FlowsRunFlowResponse, FlowsTriggerNodeData, FlowsTriggerNodeResponse, FlowsCancelNodeData, FlowsCancelNodeResponse, FlowsReadFlowStateData, FlowsReadFlowStateResponse, FlowsReadMessageHistoryData, FlowsReadMessageHistoryResponse, LoginLoginAccessTokenData, LoginLoginAccessTokenResponse, LoginTestTokenResponse, LoginRecoverPasswordData, LoginRecoverPasswordResponse, LoginResetPasswordData, LoginResetPasswordResponse, LoginRecoverPasswordHtmlContentData, LoginRecoverPasswordHtmlContentResponse, MessagesReadMessagesResponse, MessagesPublishMessageData, MessagesPublishMessageResponse, MessagesReadMessageHistoryData, MessagesReadMessageHistoryResponse, ModulesReadModulesResponse, ModulesApplyModulesData, ModulesApplyModulesResponse, OauthRegisterClientData, OauthRegisterClientResponse, OauthAuthorizeValidateData, OauthAuthorizeValidateResponse, OauthAuthorizeData, OauthAuthorizeResponse, OauthTokenData, OauthTokenResponse, OauthReadClientsResponse, OauthRevokeClientData, OauthRevokeClientResponse, PrivateCreateUserData, PrivateCreateUserResponse, SecretsReadSecretsResponse, SecretsSaveSecretData, SecretsSaveSecretResponse, SecretsDeleteSecretData, SecretsDeleteSecretResponse, UsersReadUsersData, UsersReadUsersResponse, UsersCreateUserData, UsersCreateUserResponse, UsersReadUserMeResponse, UsersDeleteUserMeResponse, UsersUpdateUserMeData, UsersUpdateUserMeResponse, UsersUpdatePasswordMeData, UsersUpdatePasswordMeResponse, UsersRegisterUserData, UsersRegisterUserResponse, UsersReadUserByIdData, UsersReadUserByIdResponse, UsersUpdateUserData, UsersUpdateUserResponse, UsersDeleteUserData, UsersDeleteUserResponse, UtilsTestEmailData, UtilsTestEmailResponse, UtilsHealthCheckResponse, UtilsHealthResponse } from './types.gen';
|
||||
|
||||
export class AlertsService {
|
||||
/**
|
||||
@@ -670,6 +670,32 @@ export class FlowsService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel Node
|
||||
* Stop a node that is running right now, by killing the worker running it.
|
||||
*
|
||||
* Idempotent on purpose: by the time a click reaches here the node may well
|
||||
* have finished, and that is the outcome that was asked for.
|
||||
* @param data The data for the request.
|
||||
* @param data.name
|
||||
* @param data.nodeId
|
||||
* @returns Message Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static cancelNode(data: FlowsCancelNodeData): CancelablePromise<FlowsCancelNodeResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'POST',
|
||||
url: '/api/v1/flows/{name}/nodes/{node_id}/cancel',
|
||||
path: {
|
||||
name: data.name,
|
||||
node_id: data.nodeId
|
||||
},
|
||||
errors: {
|
||||
422: 'Validation Error'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Read Flow State
|
||||
* The last value seen on every message of this flow.
|
||||
@@ -878,6 +904,44 @@ export class MessagesService {
|
||||
}
|
||||
}
|
||||
|
||||
export class ModulesService {
|
||||
/**
|
||||
* Read Modules
|
||||
* What node code can import, and whether it matches the manifest.
|
||||
* @returns ModulesInfo Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static readModules(): CancelablePromise<ModulesReadModulesResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/api/v1/modules/'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Modules
|
||||
* Install exactly these requirements, then hand them to the workers.
|
||||
*
|
||||
* A manifest that does not resolve changes nothing: the venv is left as it
|
||||
* was and the stored manifest is only written once the install succeeded.
|
||||
* @param data The data for the request.
|
||||
* @param data.requestBody
|
||||
* @returns ApplyResult Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static applyModules(data: ModulesApplyModulesData): CancelablePromise<ModulesApplyModulesResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'POST',
|
||||
url: '/api/v1/modules/apply',
|
||||
body: data.requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
422: 'Validation Error'
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class OauthService {
|
||||
/**
|
||||
* Register Client
|
||||
|
||||
@@ -43,6 +43,18 @@ export type app__flow__schemas__MessageValue = {
|
||||
ts?: (number | null);
|
||||
};
|
||||
|
||||
/**
|
||||
* A pip manifest, one requirement per line.
|
||||
*/
|
||||
export type ApplyRequest = {
|
||||
requirements?: string;
|
||||
};
|
||||
|
||||
export type ApplyResult = {
|
||||
ok: boolean;
|
||||
output?: string;
|
||||
};
|
||||
|
||||
export type Body_login_login_access_token = {
|
||||
grant_type?: (string | null);
|
||||
username: string;
|
||||
@@ -308,6 +320,25 @@ export type MessagesPublic = {
|
||||
count: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* One package installed in the venv node code runs on.
|
||||
*/
|
||||
export type ModulePackage = {
|
||||
name: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The venv node code imports from, and the manifest that describes it.
|
||||
*/
|
||||
export type ModulesInfo = {
|
||||
python_version?: string;
|
||||
venv_path?: string;
|
||||
requirements?: string;
|
||||
packages?: Array<ModulePackage>;
|
||||
applied?: boolean;
|
||||
};
|
||||
|
||||
export type NewPassword = {
|
||||
token: string;
|
||||
new_password: string;
|
||||
@@ -327,6 +358,10 @@ export type NodeDef_Input = {
|
||||
requires?: Array<MessageSpec>;
|
||||
provides?: Array<MessageSpec>;
|
||||
source_ref?: (string | null);
|
||||
/**
|
||||
* Seconds this node's code may run before it is stopped. Above 60 the engine may deliver its work again while it is still running.
|
||||
*/
|
||||
timeout?: (number | null);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -343,6 +378,10 @@ export type NodeDef_Output = {
|
||||
requires?: Array<MessageSpec>;
|
||||
provides?: Array<MessageSpec>;
|
||||
source_ref?: (string | null);
|
||||
/**
|
||||
* Seconds this node's code may run before it is stopped. Above 60 the engine may deliver its work again while it is still running.
|
||||
*/
|
||||
timeout?: (number | null);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -805,6 +844,13 @@ export type FlowsTriggerNodeData = {
|
||||
|
||||
export type FlowsTriggerNodeResponse = (FlowStatePublic);
|
||||
|
||||
export type FlowsCancelNodeData = {
|
||||
name: string;
|
||||
nodeId: string;
|
||||
};
|
||||
|
||||
export type FlowsCancelNodeResponse = (Message);
|
||||
|
||||
export type FlowsReadFlowStateData = {
|
||||
name: string;
|
||||
};
|
||||
@@ -859,6 +905,14 @@ export type MessagesReadMessageHistoryData = {
|
||||
|
||||
export type MessagesReadMessageHistoryResponse = (MessagePoints);
|
||||
|
||||
export type ModulesReadModulesResponse = (ModulesInfo);
|
||||
|
||||
export type ModulesApplyModulesData = {
|
||||
requestBody: ApplyRequest;
|
||||
};
|
||||
|
||||
export type ModulesApplyModulesResponse = (ApplyResult);
|
||||
|
||||
export type OauthRegisterClientData = {
|
||||
requestBody: OAuthClientRegister;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user