Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion flo_ai/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "flo_ai"
version = "1.1.0-rc6"
version = "1.1.0-rc7"
description = "A easy way to create structured AI agents"
authors = [{ name = "rootflo", email = "engineering.tools@rootflo.ai" }]
requires-python = ">=3.10,<4.0"
Expand Down
4 changes: 2 additions & 2 deletions flo_ai/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const defaultYamlContent = `service:
path: /users
backend_path: /api/users
method: GET
description: get users api
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Improve the description text quality.

The description text "get users api" is lowercase and could be more professional and descriptive. Consider using proper capitalization and a more informative description.

🔎 Suggested improvements
-      description: get users api
+      description: Retrieves a list of users

Or alternatively:

-      description: get users api
+      description: Get users API
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
description: get users api
description: Retrieves a list of users
🤖 Prompt for AI Agents
In
wavefront/client/src/pages/apps/[appId]/api-services/CreateApiServiceDialog.tsx
around line 51, the API description text is "get users api" which is
unprofessional and lacks proper capitalization and detail; update the string to
a clearer, capitalized and informative description (e.g., "Get Users API -
Returns a list of users with pagination and filters" or similar) and ensure it
follows the project's capitalization and style conventions.

output_mapper_enabled: false
`;

Expand Down
213 changes: 212 additions & 1 deletion wavefront/client/src/pages/apps/[appId]/api-services/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,29 @@ const keyValuePairSchema = z.object({
value: z.string(),
});

const payloadFieldSchema = z.object({
name: z.string(),
type: z.enum(['string', 'integer', 'number', 'boolean', 'array', 'object']),
required: z.boolean(),
description: z.string(),
});

const apiEndpointSchema = z.object({
id: z.string(),
version: z.string(),
path: z.string(),
backend_path: z.string(),
method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']),
description: z.string().optional(),
additional_headers: z.array(keyValuePairSchema),
backend_query_params: z.array(keyValuePairSchema),
output_mapper_enabled: z.boolean(),
output_mapper: z.array(keyValuePairSchema),
payload_schema: z
.object({
fields: z.array(payloadFieldSchema),
})
.optional(),
});

const apiServiceFormSchema = z.object({
Expand Down Expand Up @@ -93,10 +106,19 @@ type ParsedYamlService = {
path?: string;
backend_path?: string;
method?: string;
description?: string;
additional_headers?: Record<string, string>;
backend_query_params?: Record<string, unknown>;
output_mapper_enabled?: boolean;
output_mapper?: Record<string, string>;
payload_schema?: {
fields: Array<{
name: string;
type: 'string' | 'integer' | 'number' | 'boolean' | 'array' | 'object';
required: boolean;
description: string;
}>;
};
[key: string]: unknown;
}>;
};
Expand Down Expand Up @@ -195,12 +217,13 @@ const ApiServiceDetail: React.FC = () => {
});
}

return {
const result: Record<string, unknown> = {
id: api.id,
version: api.version,
path: api.path,
backend_path: api.backend_path,
method: api.method,
...(api.description && { description: api.description }),
...(Object.keys(apiHeadersObj).length > 0 && {
additional_headers: apiHeadersObj,
}),
Expand All @@ -213,6 +236,24 @@ const ApiServiceDetail: React.FC = () => {
output_mapper: outputMapperObj,
}),
};

// Add payload_schema for POST, PUT, PATCH methods
if (
['POST', 'PUT', 'PATCH'].includes(api.method) &&
api.payload_schema?.fields &&
api.payload_schema.fields.length > 0
) {
result.payload_schema = {
fields: api.payload_schema.fields.map((field) => ({
name: field.name,
type: field.type,
required: field.required,
...(field.description && { description: field.description }),
})),
};
}

return result;
}),
},
};
Expand Down Expand Up @@ -268,16 +309,29 @@ const ApiServiceDetail: React.FC = () => {
value: String(value),
}));

const payload_schema = api.payload_schema?.fields
? {
fields: api.payload_schema.fields.map((field) => ({
name: field.name,
type: field.type,
required: field.required,
description: field.description,
})),
}
: { fields: [] };

return {
id: api.id || '',
version: api.version || 'v1',
path: api.path || '',
backend_path: api.backend_path || '',
method: (api.method as 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH') || 'GET',
description: api.description || '',
additional_headers: api_additional_headers,
backend_query_params: backend_query_params,
output_mapper_enabled: api.output_mapper_enabled || false,
output_mapper: output_mapper,
payload_schema: payload_schema,
};
}),
};
Expand Down Expand Up @@ -329,16 +383,29 @@ const ApiServiceDetail: React.FC = () => {
value: String(value),
}));

const payload_schema = api.payload_schema?.fields
? {
fields: api.payload_schema.fields.map((field) => ({
name: field.name,
type: field.type,
required: field.required,
description: field.description,
})),
}
: { fields: [] };

return {
id: api.id,
version: api.version,
path: api.path,
backend_path: api.backend_path || '',
method: api.method as 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH',
description: api.description || '',
additional_headers: api_additional_headers,
backend_query_params: backend_query_params,
output_mapper_enabled: api.output_mapper_enabled || false,
output_mapper: output_mapper,
payload_schema: payload_schema,
};
}),
};
Expand Down Expand Up @@ -458,10 +525,14 @@ const ApiServiceDetail: React.FC = () => {
path: '',
backend_path: '',
method: 'GET',
description: '',
additional_headers: [],
backend_query_params: [],
output_mapper_enabled: false,
output_mapper: [],
payload_schema: {
fields: [],
},
});
};

Expand Down Expand Up @@ -508,6 +579,22 @@ const ApiServiceDetail: React.FC = () => {
);
};

const handleAddPayloadField = (apiIndex: number) => {
const currentFields = form.getValues(`apis.${apiIndex}.payload_schema.fields`) || [];
form.setValue(`apis.${apiIndex}.payload_schema.fields`, [
...currentFields,
{ name: '', type: 'string', required: false, description: '' },
]);
};

const handleRemovePayloadField = (apiIndex: number, fieldIndex: number) => {
const currentFields = form.getValues(`apis.${apiIndex}.payload_schema.fields`) || [];
form.setValue(
`apis.${apiIndex}.payload_schema.fields`,
currentFields.filter((_, i) => i !== fieldIndex)
);
};

return (
<div className="h-full bg-white px-8 pt-8 pb-[200px]">
<Breadcrumb className="mb-6">
Expand Down Expand Up @@ -940,6 +1027,27 @@ const ApiServiceDetail: React.FC = () => {
/>
</div>

{/* API Description */}
<div className="mt-6 w-full">
<FormField
control={form.control}
name={`apis.${index}.description`}
render={({ field }) => (
<FormItem>
<FormLabel>Description (Optional)</FormLabel>
<FormControl>
<Input
placeholder="e.g., Fetches all users from the system"
disabled={!editing}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>

{/* API Additional Headers */}
<div className="mt-6 w-full">
<div className="mb-4 flex items-center justify-between">
Expand Down Expand Up @@ -1054,6 +1162,109 @@ const ApiServiceDetail: React.FC = () => {
</div>
</div>

{/* Payload Schema - Only for POST, PUT, PATCH */}
{['POST', 'PUT', 'PATCH'].includes(form.watch(`apis.${index}.method`)) && (
<div className="mt-6 w-full">
<div className="mb-4 flex items-center justify-between">
<Label className="text-sm font-medium">Payload Schema</Label>
<Button
type="button"
onClick={() => handleAddPayloadField(index)}
variant="outline"
size="icon"
disabled={!editing}
>
<Plus />
</Button>
</div>
<div className="flex w-full flex-col gap-3">
{(form.watch(`apis.${index}.payload_schema.fields`) || []).map(
(_field: unknown, fIndex: number) => (
<div key={fIndex} className="flex w-full gap-3">
<FormField
control={form.control}
name={`apis.${index}.payload_schema.fields.${fIndex}.name`}
render={({ field }) => (
<FormItem className="flex-1">
<FormControl>
<Input placeholder="Field Name" disabled={!editing} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`apis.${index}.payload_schema.fields.${fIndex}.type`}
render={({ field }) => (
<FormItem className="flex-1">
<Select
onValueChange={field.onChange}
value={field.value}
disabled={!editing}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Type" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="string">String</SelectItem>
<SelectItem value="integer">Integer</SelectItem>
<SelectItem value="number">Number</SelectItem>
<SelectItem value="boolean">Boolean</SelectItem>
<SelectItem value="array">Array</SelectItem>
<SelectItem value="object">Object</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`apis.${index}.payload_schema.fields.${fIndex}.required`}
render={({ field }) => (
<FormItem className="flex flex-row items-center space-y-0 space-x-2 pt-2">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
disabled={!editing}
/>
</FormControl>
<FormLabel className="mb-0 text-xs">Required</FormLabel>
</FormItem>
)}
/>
<FormField
control={form.control}
name={`apis.${index}.payload_schema.fields.${fIndex}.description`}
render={({ field }) => (
<FormItem className="flex-1">
<FormControl>
<Input placeholder="Description" disabled={!editing} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="button"
onClick={() => handleRemovePayloadField(index, fIndex)}
variant="outline"
size="icon"
disabled={!editing}
>
<Trash2 color="#DD5252" />
</Button>
</div>
)
)}
</div>
</div>
)}

{/* Output Mapper */}
<div className="mt-6 w-full border-t border-gray-200 pt-6">
<div className="mb-4 flex items-center justify-between">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const createMessageProcessorSchema = z.object({
type CreateMessageProcessorInput = z.infer<typeof createMessageProcessorSchema>;

const defaultYamlContent = `type: javascript
description: double the number
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, locate and read the CreateFunctionDialog.tsx file
fd "CreateFunctionDialog.tsx" --type f

Repository: rootflo/wavefront

Length of output: 135


🏁 Script executed:

# Read the CreateFunctionDialog.tsx file
cat -n "wavefront/client/src/pages/apps/[appId]/functions/CreateFunctionDialog.tsx"

Repository: rootflo/wavefront

Length of output: 7609


🏁 Script executed:

# Search for messageProcessorService implementation
fd "messageProcessor" -t f | head -20

Repository: rootflo/wavefront

Length of output: 43


🏁 Script executed:

# Search for messageProcessorService references in the codebase
rg "messageProcessorService" --type ts --type tsx -A 3 | head -50

Repository: rootflo/wavefront

Length of output: 88


🏁 Script executed:

# Search for messageProcessorService references
rg "messageProcessorService" -A 3 | head -80

Repository: rootflo/wavefront

Length of output: 3157


🏁 Script executed:

# Find MessageProcessorService implementation
fd "MessageProcessor" -t f | grep -i service

Repository: rootflo/wavefront

Length of output: 43


🏁 Script executed:

# Search for MessageProcessorService class definition
rg "class MessageProcessorService" -A 30

Repository: rootflo/wavefront

Length of output: 6930


🏁 Script executed:

# Check the UpdateMessageProcessorRequest to see what fields are sent during update
rg "UpdateMessageProcessorRequest\|CreateMessageProcessorRequest" -A 10

Repository: rootflo/wavefront

Length of output: 43


🏁 Script executed:

# Search for message processor type definitions
rg "interface.*MessageProcessor\|type.*MessageProcessor" -A 5 | head -100

Repository: rootflo/wavefront

Length of output: 43


🏁 Script executed:

# Check the message-processor-service.ts file for type definitions
cat "wavefront/client/src/api/message-processor-service.ts"

Repository: rootflo/wavefront

Length of output: 2278


🏁 Script executed:

# Find message processor types
fd "message-processor" -t f | grep type

Repository: rootflo/wavefront

Length of output: 108


🏁 Script executed:

# Read the message processor types file
cat "wavefront/client/src/types/message-processor.ts"

Repository: rootflo/wavefront

Length of output: 1360


🏁 Script executed:

# Check the update/edit dialog to see how it handles descriptions
cat -n "wavefront/client/src/pages/apps/[appId]/functions/[functionId].tsx" | head -200

Repository: rootflo/wavefront

Length of output: 8347


🏁 Script executed:

# Continue reading the edit page form section
cat -n "wavefront/client/src/pages/apps/[appId]/functions/[functionId].tsx" | sed -n '200,400p'

Repository: rootflo/wavefront

Length of output: 6171


Clarify the relationship between YAML description and processor description metadata.

The default YAML template includes a description: double the number field (line 38), and the form also has a separate optional description input field (lines 147-159). Both are sent to the API as independent values:

  • yaml_content (the YAML specification, which includes the function's description)
  • description (processor metadata from the form)

While these serve different purposes (function specification vs. system metadata), this creates a potential UX issue:

  • Users can edit the description inside the YAML editor, which only persists as part of the YAML specification
  • Users can independently fill the form's description field, which stores metadata
  • When editing a processor, the form displays the stored metadata description, not the YAML description
  • Changes to the YAML description won't sync with the processor's metadata description

Consider adding clarity in the UI (e.g., labels or help text) to explain the distinction: the YAML description is part of the function specification, while the form description is the processor's metadata in the system.

🤖 Prompt for AI Agents
In wavefront/client/src/pages/apps/[appId]/functions/CreateFunctionDialog.tsx
around lines 38 and 147-159, the default YAML template contains a "description:
double the number" while the form has a separate processor metadata description,
which can confuse users because edits to one do not update the other; update the
UI to clarify the distinction by adding concise helper text or labels: (1) near
the YAML editor indicate "YAML description is part of the function specification
and will be saved inside yaml_content", and (2) next to the form description
input indicate "Processor metadata description — stored separately as
description and shown when editing processors"; alternatively add a small
tooltip or placeholder on both fields explaining they are independent and, if
desired, recommend the canonical source (YAML vs metadata) and whether changes
are synced or not.

function:
code: |
export default function(input) {
Expand Down
13 changes: 13 additions & 0 deletions wavefront/client/src/types/api-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,29 @@ export interface ApiAuth {
api_key_header?: string;
}

export interface PayloadField {
name: string;
type: 'string' | 'integer' | 'number' | 'boolean' | 'array' | 'object';
required: boolean;
description: string;
}
Comment on lines +17 to +22
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for PayloadField class definition in Python files
rg -n -A 10 'class PayloadField' --type=py

Repository: rootflo/wavefront

Length of output: 1255


Make description field optional in PayloadField interface to match backend contract.

The backend PayloadFieldSchema defines description: str = '' (optional with empty string default), but the frontend PayloadField interface defines description: string as required. This mismatch violates type consistency—the frontend should allow optional descriptions to align with the server-side schema.

Change line 21 from:

description: string;

to:

description?: string;
🤖 Prompt for AI Agents
In @wavefront/client/src/types/api-service.ts around lines 17-22, The
PayloadField interface currently requires description but the backend allows an
optional description; update the PayloadField type by making the description
property optional (i.e., change description to be optional) so the frontend
matches the backend contract; locate the interface named PayloadField in
api-service.ts and modify the description property accordingly.


export interface PayloadSchema {
fields: PayloadField[];
}

export interface ApiEndpoint {
id: string;
version: string;
path: string;
backend_path: string;
method: string;
description?: string;
additional_headers?: Record<string, string>;
backend_query_params?: Record<string, unknown>;
output_mapper_enabled: boolean;
output_mapper?: Record<string, string>;
payload_schema?: PayloadSchema;
}

export interface ApiServiceItem {
Expand Down
Loading
Loading