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 app/api/ai/generate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ Action types:
- Database Query: {"actionType": "Database Query", "dbQuery": "SELECT * FROM table", "dbTable": "table"}
- HTTP Request: {"actionType": "HTTP Request", "httpMethod": "POST", "endpoint": "https://api.example.com", "httpHeaders": "{}", "httpBody": "{}"}
- Generate Text: {"actionType": "Generate Text", "aiModel": "gpt-5", "aiFormat": "text", "aiPrompt": "Your prompt here"}
- Generate Image: {"actionType": "Generate Image", "imageModel": "openai/dall-e-3", "imagePrompt": "Image description"}
- Generate Image: {"actionType": "Generate Image", "imageModel": "bfl/flux-2-pro", "imagePrompt": "Image description"}
- Condition: {"actionType": "Condition", "condition": "{{@nodeId:Label.field}} === 'value'"}

CRITICAL ABOUT CONDITION NODES:
Expand Down
11 changes: 2 additions & 9 deletions components/workflow/config/action-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -501,20 +501,13 @@ function GenerateImageFields({
<Select
disabled={disabled}
onValueChange={(value) => onUpdateConfig("imageModel", value)}
value={(config?.imageModel as string) || "openai/dall-e-3"}
value={(config?.imageModel as string) || "bfl/flux-2-pro"}
>
<SelectTrigger className="w-full" id="imageModel">
<SelectValue placeholder="Select model" />
</SelectTrigger>
<SelectContent>
<SelectItem value="openai/dall-e-3">OpenAI DALL-E 3</SelectItem>
<SelectItem value="openai/dall-e-2">OpenAI DALL-E 2</SelectItem>
<SelectItem value="google/gemini-2.5-flash-image">
Google Gemini 2.5 Flash Image
</SelectItem>
<SelectItem value="google/gemini-2.5-flash-image-preview">
Google Gemini 2.5 Flash Image Preview
</SelectItem>
<SelectItem value="bfl/flux-2-pro">FLUX.2 Pro</SelectItem>
</SelectContent>
</Select>
</div>
Expand Down
18 changes: 10 additions & 8 deletions lib/codegen-templates/generate-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,24 @@
* Code template for Generate Image action step
* This is a string template used for code generation - keep as string export
*/
export default `import OpenAI from 'openai';
export default `import { experimental_generateImage as generateImage } from 'ai';

export async function generateImageStep(input: {
model: string;
prompt: string;
}) {
"use step";

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

const response = await openai.images.generate({
model: input.model,
const result = await generateImage({
model: input.model as any,
prompt: input.prompt,
n: 1,
response_format: 'b64_json',
size: '1024x1024',
providerOptions: {
openai: {
apiKey: process.env.AI_GATEWAY_API_KEY,
},
},
});

return { base64: response.data[0].b64_json };
return { base64: result.image.toString() };
}`;
4 changes: 0 additions & 4 deletions lib/credential-fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export type WorkflowCredentials = {
LINEAR_TEAM_ID?: string;
SLACK_API_KEY?: string;
AI_GATEWAY_API_KEY?: string;
OPENAI_API_KEY?: string;
DATABASE_URL?: string;
};

Expand Down Expand Up @@ -71,9 +70,6 @@ function mapAiGatewayConfig(config: IntegrationConfig): WorkflowCredentials {
if (config.apiKey) {
creds.AI_GATEWAY_API_KEY = config.apiKey;
}
if (config.openaiApiKey) {
creds.OPENAI_API_KEY = config.openaiApiKey;
}
return creds;
}

Expand Down
28 changes: 17 additions & 11 deletions lib/steps/generate-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/
import "server-only";

import OpenAI from "openai";
import { experimental_generateImage as generateImage } from "ai";
import { fetchCredentials } from "../credential-fetcher";

export async function generateImageStep(input: {
Expand All @@ -20,26 +20,32 @@ export async function generateImageStep(input: {
? await fetchCredentials(input.integrationId)
: {};

const apiKey = credentials.OPENAI_API_KEY || credentials.AI_GATEWAY_API_KEY;
const apiKey = credentials.AI_GATEWAY_API_KEY;

if (!apiKey) {
throw new Error(
"OPENAI_API_KEY or AI_GATEWAY_API_KEY is not configured. Please add it in Project Integrations."
"AI_GATEWAY_API_KEY is not configured. Please add it in Project Integrations."
);
}

const openai = new OpenAI({ apiKey });

const response = await openai.images.generate({
model: input.model,
const result = await generateImage({
// biome-ignore lint/suspicious/noExplicitAny: model string needs type coercion for ai package
model: input.model as any,
prompt: input.prompt,
n: 1,
response_format: "b64_json",
size: "1024x1024",
providerOptions: {
openai: {
apiKey,
},
},
});

if (!response.data?.[0]) {
if (!result.image) {
throw new Error("Failed to generate image");
}

return { base64: response.data[0].b64_json };
// Convert the GeneratedFile to base64 string
const base64 = result.image.toString();

return { base64 };
}
4 changes: 2 additions & 2 deletions lib/steps/generate-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ export async function generateTextStep(input: {
? await fetchCredentials(input.integrationId)
: {};

const apiKey = credentials.AI_GATEWAY_API_KEY || credentials.OPENAI_API_KEY;
const apiKey = credentials.AI_GATEWAY_API_KEY;

if (!apiKey) {
throw new Error(
"AI_GATEWAY_API_KEY or OPENAI_API_KEY is not configured. Please add it in Project Integrations."
"AI_GATEWAY_API_KEY is not configured. Please add it in Project Integrations."
);
}

Expand Down
13 changes: 8 additions & 5 deletions lib/workflow-codegen-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,14 +304,14 @@ function _generateGenerateImageStepBody(
const imagePrompt = (config.imagePrompt as string) || "";
const convertedImagePrompt = convertTemplateToJS(imagePrompt);

return ` const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
return ` const openai = new OpenAI({ apiKey: process.env.AI_GATEWAY_API_KEY });

// Use template literal with dynamic values from outputs
const imagePrompt = \`${escapeForTemplateLiteral(convertedImagePrompt)}\`;
const finalPrompt = (input.imagePrompt as string) || imagePrompt;

const response = await openai.images.generate({
model: '${config.imageModel || "dall-e-3"}',
model: '${config.imageModel || "bfl/flux-2-pro"}',
prompt: finalPrompt,
n: 1,
response_format: 'b64_json',
Expand Down Expand Up @@ -503,12 +503,15 @@ export function generateWorkflowSDKCode(
}

function buildAIImageParams(config: Record<string, unknown>): string[] {
imports.add("import OpenAI from 'openai';");
const imageModel = (config.imageModel as string) || "dall-e-3";
imports.add(
"import { experimental_generateImage as generateImage } from 'ai';"
);
const imageModel = (config.imageModel as string) || "bfl/flux-2-pro";
return [
`model: "${imageModel}"`,
`prompt: \`${convertTemplateToJS((config.imagePrompt as string) || "")}\``,
"apiKey: process.env.OPENAI_API_KEY!",
'size: "1024x1024"',
"providerOptions: { openai: { apiKey: process.env.AI_GATEWAY_API_KEY! } }",
];
}

Expand Down
7 changes: 5 additions & 2 deletions lib/workflow-codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,17 +438,20 @@ export function generateWorkflowCode(
indent: string,
varName: string
): string[] {
imports.add("import { generateImage } from './integrations/ai';");
imports.add(
Copy link
Contributor

Choose a reason for hiding this comment

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

The generateAiImageActionCode function generates incomplete code that's missing the required API key configuration. This will cause runtime failures when the generated code tries to call the generateImage function without authentication.

View Details
📝 Patch Details
diff --git a/lib/workflow-codegen.ts b/lib/workflow-codegen.ts
index 9d74a99..4472d59 100644
--- a/lib/workflow-codegen.ts
+++ b/lib/workflow-codegen.ts
@@ -452,6 +452,11 @@ export function generateWorkflowCode(
       `${indent}  model: "${imageModel}",`,
       `${indent}  prompt: \`${imagePrompt}\`,`,
       `${indent}  size: "1024x1024",`,
+      `${indent}  providerOptions: {`,
+      `${indent}    openai: {`,
+      `${indent}      apiKey: process.env.AI_GATEWAY_API_KEY!`,
+      `${indent}    },`,
+      `${indent}  },`,
       `${indent}});`,
     ];
   }

Analysis

Missing API key configuration in generateAiImageActionCode

What fails: generateAiImageActionCode() in lib/workflow-codegen.ts (lines 436-457) generates code that calls experimental_generateImage without the required providerOptions.openai.apiKey parameter, causing authentication failures when the generated code is executed.

How to reproduce:

  1. Create a workflow with a "Generate Image" action
  2. Download/export the generated code via the API endpoint
  3. Execute the generated code without setting OPENAI_API_KEY environment variable
  4. The generateImage() call fails with authentication error

Result: Generated code produces authentication error:

Error: OpenAI API request failed: 401 Unauthorized - Incorrect API key provided

Expected: According to Vercel AI SDK documentation, providerOptions parameter can be used to pass provider-specific settings including API keys. The generated code should match the pattern used in:

  • lib/codegen-templates/generate-image.ts (lines 17-21) - template version includes providerOptions
  • lib/workflow-codegen-sdk.ts (line 514) - SDK version includes providerOptions
  • lib/steps/generate-image.ts (lines 36-40) - step function version includes providerOptions

All three existing patterns consistently pass providerOptions: { openai: { apiKey: process.env.AI_GATEWAY_API_KEY } } to ensure authentication works correctly.

Fix: Added providerOptions parameter to the generated code in generateAiImageActionCode() to match the existing patterns used throughout the codebase.

"import { experimental_generateImage as generateImage } from 'ai';"
);
const imagePrompt =
(node.data.config?.imagePrompt as string) || "A beautiful landscape";
const imageModel =
(node.data.config?.imageModel as string) || "openai/dall-e-3";
(node.data.config?.imageModel as string) || "bfl/flux-2-pro";

return [
`${indent}// Generate image using AI`,
`${indent}const ${varName} = await generateImage({`,
`${indent} model: "${imageModel}",`,
`${indent} prompt: \`${imagePrompt}\`,`,
`${indent} size: "1024x1024",`,
`${indent}});`,
];
}
Expand Down