diff --git a/skyvern-frontend/src/routes/tasks/constants.ts b/skyvern-frontend/src/routes/tasks/constants.ts
index 5ef99a2b..25d3c012 100644
--- a/skyvern-frontend/src/routes/tasks/constants.ts
+++ b/skyvern-frontend/src/routes/tasks/constants.ts
@@ -1 +1,3 @@
export const PAGE_SIZE = 15;
+
+export const MAX_STEPS_DEFAULT = 10;
diff --git a/skyvern-frontend/src/routes/tasks/create/CreateNewTaskForm.tsx b/skyvern-frontend/src/routes/tasks/create/CreateNewTaskForm.tsx
index d635f8ad..141077f5 100644
--- a/skyvern-frontend/src/routes/tasks/create/CreateNewTaskForm.tsx
+++ b/skyvern-frontend/src/routes/tasks/create/CreateNewTaskForm.tsx
@@ -46,6 +46,7 @@ import {
} from "@/components/ui/accordion";
import { OrganizationApiResponse } from "@/api/types";
import { Skeleton } from "@/components/ui/skeleton";
+import { MAX_STEPS_DEFAULT } from "../constants";
const createNewTaskFormSchema = z
.object({
@@ -94,7 +95,6 @@ function createTaskRequestObject(formValues: CreateNewTaskFormValues) {
navigation_goal: transform(formValues.navigationGoal),
data_extraction_goal: transform(formValues.dataExtractionGoal),
proxy_location: "RESIDENTIAL",
- error_code_mapping: null,
navigation_payload: transform(formValues.navigationPayload),
extracted_information_schema: transform(
formValues.extractedInformationSchema,
@@ -102,8 +102,6 @@ function createTaskRequestObject(formValues: CreateNewTaskFormValues) {
};
}
-const MAX_STEPS_DEFAULT = 10;
-
function CreateNewTaskForm({ initialValues }: Props) {
const queryClient = useQueryClient();
const { toast } = useToast();
diff --git a/skyvern-frontend/src/routes/tasks/create/CreateNewTaskFormPage.tsx b/skyvern-frontend/src/routes/tasks/create/CreateNewTaskFormPage.tsx
index f6d3d114..6070aa85 100644
--- a/skyvern-frontend/src/routes/tasks/create/CreateNewTaskFormPage.tsx
+++ b/skyvern-frontend/src/routes/tasks/create/CreateNewTaskFormPage.tsx
@@ -48,6 +48,8 @@ function CreateNewTaskFormPage() {
const dataSchema = data.workflow_definition.blocks[0].data_schema;
+ const maxSteps = data.workflow_definition.blocks[0].max_steps_per_run;
+
return (
);
diff --git a/skyvern-frontend/src/routes/tasks/create/SavedTaskForm.tsx b/skyvern-frontend/src/routes/tasks/create/SavedTaskForm.tsx
index 3189abf6..7dd33556 100644
--- a/skyvern-frontend/src/routes/tasks/create/SavedTaskForm.tsx
+++ b/skyvern-frontend/src/routes/tasks/create/SavedTaskForm.tsx
@@ -24,7 +24,7 @@ import { apiBaseUrl } from "@/util/env";
import { zodResolver } from "@hookform/resolvers/zod";
import { InfoCircledIcon, ReloadIcon } from "@radix-ui/react-icons";
import { ToastAction } from "@radix-ui/react-toast";
-import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import fetchToCurl from "fetch-to-curl";
import { useForm, useFormState } from "react-hook-form";
import { Link, useParams } from "react-router-dom";
@@ -46,6 +46,9 @@ import {
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
+import { OrganizationApiResponse } from "@/api/types";
+import { MAX_STEPS_DEFAULT } from "../constants";
+import { Skeleton } from "@/components/ui/skeleton";
const savedTaskFormSchema = z
.object({
@@ -60,6 +63,7 @@ const savedTaskFormSchema = z
dataExtractionGoal: z.string().or(z.null()).optional(),
navigationPayload: z.string().or(z.null()).optional(),
extractedInformationSchema: z.string().or(z.null()).optional(),
+ maxSteps: z.number().optional(),
})
.superRefine(
(
@@ -154,6 +158,7 @@ function createTaskTemplateRequestObject(values: SavedTaskFormValues) {
navigation_goal: values.navigationGoal,
data_extraction_goal: values.dataExtractionGoal,
data_schema: extractedInformationSchema,
+ max_steps_per_run: values.maxSteps,
},
],
},
@@ -167,9 +172,30 @@ function SavedTaskForm({ initialValues }: Props) {
const apiCredential = useApiCredential();
const { template } = useParams();
+ const { data: organizations, isPending: organizationIsPending } = useQuery<
+ Array
+ >({
+ queryKey: ["organizations"],
+ queryFn: async () => {
+ const client = await getClient(credentialGetter);
+ return await client
+ .get("/organizations")
+ .then((response) => response.data.organizations);
+ },
+ });
+
+ const organization = organizations?.[0];
+
const form = useForm({
resolver: zodResolver(savedTaskFormSchema),
defaultValues: initialValues,
+ values: {
+ ...initialValues,
+ maxSteps:
+ initialValues.maxSteps ??
+ organization?.max_steps_per_run ??
+ MAX_STEPS_DEFAULT,
+ },
});
const { isDirty } = useFormState({ control: form.control });
@@ -178,10 +204,19 @@ function SavedTaskForm({ initialValues }: Props) {
mutationFn: async (formValues: SavedTaskFormValues) => {
const taskRequest = createTaskRequestObject(formValues);
const client = await getClient(credentialGetter);
+ const includeOverrideHeader =
+ formValues.maxSteps !== organization?.max_steps_per_run &&
+ formValues.maxSteps !== MAX_STEPS_DEFAULT;
return client.post<
ReturnType,
{ data: { task_id: string } }
- >("/tasks", taskRequest);
+ >("/tasks", taskRequest, {
+ ...(includeOverrideHeader && {
+ headers: {
+ "x-max-steps-override": formValues.maxSteps ?? MAX_STEPS_DEFAULT,
+ },
+ }),
+ });
},
onError: (error: AxiosError) => {
if (error.response?.status === 402) {
@@ -516,6 +551,40 @@ function SavedTaskForm({ initialValues }: Props) {
)}
/>
+ {
+ return (
+
+ Max Steps
+
+ Max steps for this task. This will override your
+ organization wide setting.
+
+
+ {organizationIsPending ? (
+
+ ) : (
+ {
+ field.onChange(parseInt(event.target.value));
+ }}
+ />
+ )}
+
+
+ );
+ }}
+ />