mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-27 17:36:25 +02:00
Merge pull request #264 from rowboatlabs/fix_image_fr
moved generate_image from being attached to the workflow
This commit is contained in:
commit
726559de76
8 changed files with 209 additions and 136 deletions
36
apps/rowboat/app/lib/default_tools.ts
Normal file
36
apps/rowboat/app/lib/default_tools.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
// Returns the list of built-in tools that should appear by default
|
||||
// in the workflow editor and be usable at runtime without attaching
|
||||
// them to the workflow. These are displayed as read-only library tools.
|
||||
// Note: avoid importing WorkflowTool here to prevent circular deps.
|
||||
// Return a structurally compatible object instead.
|
||||
export function getDefaultTools(): Array<any> {
|
||||
// Show built-in tools only when a public, non-secret flag is set.
|
||||
// Avoids exposing real secrets in client bundles.
|
||||
const hasGoogleKeyFlag = (process.env.NEXT_PUBLIC_HAS_GOOGLE_API_KEY || '').toLowerCase() === 'true';
|
||||
|
||||
if (!hasGoogleKeyFlag) return [];
|
||||
|
||||
return [
|
||||
{
|
||||
name: 'Generate Image',
|
||||
description:
|
||||
'Generate an image using Google Gemini given a text prompt. Returns base64-encoded image data and any text parts.',
|
||||
isGeminiImage: true,
|
||||
isLibrary: true,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
prompt: {
|
||||
type: 'string',
|
||||
description: 'Text prompt describing the image to generate',
|
||||
},
|
||||
modelName: { type: 'string', description: 'Optional Gemini model override' },
|
||||
},
|
||||
required: ['prompt'],
|
||||
additionalProperties: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ import { z } from 'zod';
|
|||
|
||||
// Provide a minimal default template to satisfy legacy code paths that
|
||||
// still reference `templates.default`. Real templates are DB-backed.
|
||||
const includeGeminiImageTool = !!process.env.GOOGLE_API_KEY;
|
||||
|
||||
const defaultTemplate: z.infer<typeof WorkflowTemplate> = {
|
||||
name: 'Blank Template',
|
||||
|
|
@ -11,22 +10,7 @@ const defaultTemplate: z.infer<typeof WorkflowTemplate> = {
|
|||
startAgent: "",
|
||||
agents: [],
|
||||
prompts: [],
|
||||
tools: includeGeminiImageTool ? [
|
||||
{
|
||||
name: "Generate Image",
|
||||
description: "Generate an image using Google Gemini given a text prompt. Returns base64-encoded image data and any text parts.",
|
||||
isGeminiImage: true,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
prompt: { type: 'string', description: 'Text prompt describing the image to generate' },
|
||||
modelName: { type: 'string', description: 'Optional Gemini model override' },
|
||||
},
|
||||
required: ['prompt'],
|
||||
additionalProperties: true,
|
||||
},
|
||||
},
|
||||
] : [],
|
||||
tools: [],
|
||||
pipelines: [],
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { z } from "zod";
|
||||
import { getDefaultTools } from "@/app/lib/default_tools";
|
||||
export const WorkflowAgent = z.object({
|
||||
name: z.string(),
|
||||
order: z.number().int().optional(),
|
||||
|
|
@ -165,7 +166,10 @@ export function sanitizeTextWithMentions(
|
|||
const agent = workflow.agents.find(a => a.name === entity.name);
|
||||
return agent && agent.type !== 'pipeline';
|
||||
} else if (entity.type === 'tool') {
|
||||
return workflow.tools.some(t => t.name === entity.name);
|
||||
// Allow referencing workflow tools or default library tools
|
||||
const inWorkflow = workflow.tools.some(t => t.name === entity.name);
|
||||
const inDefaults = getDefaultTools().some(t => t.name === entity.name);
|
||||
return inWorkflow || inDefaults;
|
||||
} else if (entity.type === 'prompt') {
|
||||
return workflow.prompts.some(p => p.name === entity.name);
|
||||
} else if (entity.type === 'pipeline') {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { Panel } from "@/components/common/panel-common";
|
|||
import { Button as CustomButton } from "@/components/ui/button";
|
||||
import clsx from "clsx";
|
||||
import { InputField } from "@/app/lib/components/input-field";
|
||||
import { getDefaultTools } from "@/app/lib/default_tools";
|
||||
import { USE_TRANSFER_CONTROL_OPTIONS } from "@/app/lib/feature_flags";
|
||||
import { Info as InfoIcon } from "lucide-react";
|
||||
import { useCopilot } from "../copilot/use-copilot";
|
||||
|
|
@ -236,7 +237,13 @@ export function AgentConfig({
|
|||
const atMentions = createAtMentions({
|
||||
agents: agents,
|
||||
prompts,
|
||||
tools,
|
||||
tools: (() => {
|
||||
const defaults = getDefaultTools();
|
||||
const map = new Map<string, z.infer<typeof WorkflowTool>>();
|
||||
for (const t of tools) map.set(t.name, t);
|
||||
for (const t of defaults) if (!map.has(t.name)) map.set(t.name, t as any);
|
||||
return Array.from(map.values());
|
||||
})(),
|
||||
pipelines: agent.type === "pipeline" ? [] : (workflow.pipelines || []), // Pipeline agents can't reference pipelines
|
||||
currentAgentName: agent.name,
|
||||
currentAgent: agent
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { DataSource } from "@/src/entities/models/data-source";
|
|||
import { WithStringId } from "../../../lib/types/types";
|
||||
import { Dropdown, DropdownItem, DropdownTrigger, DropdownMenu } from "@heroui/react";
|
||||
import { useRef, useEffect, useState } from "react";
|
||||
import { EllipsisVerticalIcon, ImportIcon, PlusIcon, Brain, Boxes, Wrench, PenLine, Library, ChevronDown, ChevronRight, ServerIcon, Component, ScrollText, GripVertical, Users, Cog, CheckCircle2, LinkIcon, UnlinkIcon, MoreVertical, Eye, Trash2, AlertTriangle, Circle, Database } from "lucide-react";
|
||||
import { EllipsisVerticalIcon, ImportIcon, PlusIcon, Brain, Boxes, Wrench, PenLine, Library, ChevronDown, ChevronRight, ServerIcon, Component, ScrollText, GripVertical, Users, Cog, CheckCircle2, LinkIcon, UnlinkIcon, MoreVertical, Eye, Trash2, AlertTriangle, Circle, Database, Image as ImageIcon } from "lucide-react";
|
||||
import { Tooltip } from "@heroui/react";
|
||||
import { DndContext, DragEndEvent, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
|
||||
import { SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
||||
|
|
@ -20,6 +20,7 @@ import { ServerLogo } from '../tools/components/MCPServersCommon';
|
|||
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from "@heroui/react";
|
||||
import { ToolsModal } from './components/ToolsModal';
|
||||
import { DataSourcesModal } from './components/DataSourcesModal';
|
||||
import { getDefaultTools } from "@/app/lib/default_tools";
|
||||
import { DataSourceIcon } from '../../../lib/components/datasource-icon';
|
||||
import { deleteDataSource } from '../../../actions/data-source.actions';
|
||||
import { ToolkitAuthModal } from '../tools/components/ToolkitAuthModal';
|
||||
|
|
@ -939,97 +940,121 @@ export const EntityList = forwardRef<
|
|||
{expandedPanels.tools && (
|
||||
<div className="h-full overflow-y-auto">
|
||||
<div className="p-2">
|
||||
{tools.length > 0 ? (
|
||||
<div className="space-y-1">
|
||||
{/* Group tools by server */}
|
||||
{(() => {
|
||||
// Get custom tools (non-MCP tools)
|
||||
const customTools = tools.filter(tool => !tool.isMcp && !tool.isComposio);
|
||||
|
||||
// Group MCP tools by server
|
||||
const serverTools = tools.reduce((acc, tool) => {
|
||||
if (tool.isMcp && tool.mcpServerName) {
|
||||
if (!acc[tool.mcpServerName]) {
|
||||
acc[tool.mcpServerName] = [];
|
||||
}
|
||||
acc[tool.mcpServerName].push(tool);
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, typeof tools>);
|
||||
{(() => {
|
||||
// Merge workflow tools with default library tools (unique by name)
|
||||
const defaults = getDefaultTools();
|
||||
const toolMap = new Map<string, z.infer<typeof WorkflowTool>>();
|
||||
for (const t of tools) toolMap.set(t.name, t);
|
||||
for (const t of defaults) if (!toolMap.has(t.name)) toolMap.set(t.name, t as any);
|
||||
const toolsForDisplay = Array.from(toolMap.values());
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Show composio cards - ordered by status */}
|
||||
{Object.values(composioTools)
|
||||
.map((card) => (
|
||||
<ComposioCard
|
||||
key={card.slug}
|
||||
card={card}
|
||||
selectedEntity={selectedEntity}
|
||||
onSelectTool={handleToolSelection}
|
||||
onDeleteTool={onDeleteTool}
|
||||
selectedRef={selectedRef}
|
||||
projectConfig={projectConfig}
|
||||
projectId={projectId}
|
||||
workflow={workflow}
|
||||
onProjectToolsUpdated={onProjectToolsUpdated}
|
||||
setSelectedToolkitSlug={setSelectedToolkitSlug}
|
||||
setShowToolsModal={setShowToolsModal}
|
||||
/>
|
||||
))}
|
||||
if (toolsForDisplay.length > 0) {
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
{/* Group tools by server */}
|
||||
{(() => {
|
||||
// Get custom tools (non-MCP, non-Composio)
|
||||
const customTools = toolsForDisplay.filter(tool => !tool.isMcp && !tool.isComposio);
|
||||
|
||||
{/* Show MCP server cards */}
|
||||
{Object.entries(serverTools).map(([serverName, tools]) => (
|
||||
<ServerCard
|
||||
key={serverName}
|
||||
serverName={serverName}
|
||||
tools={tools}
|
||||
selectedEntity={selectedEntity}
|
||||
onSelectTool={handleToolSelection}
|
||||
onDeleteTool={onDeleteTool}
|
||||
selectedRef={selectedRef}
|
||||
/>
|
||||
))}
|
||||
// Group MCP tools by server
|
||||
const serverTools = toolsForDisplay.reduce((acc, tool) => {
|
||||
if (tool.isMcp && tool.mcpServerName) {
|
||||
if (!acc[tool.mcpServerName]) {
|
||||
acc[tool.mcpServerName] = [];
|
||||
}
|
||||
acc[tool.mcpServerName].push(tool);
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, typeof toolsForDisplay>);
|
||||
|
||||
{/* Show custom tools */}
|
||||
{customTools.length > 0 && (
|
||||
<div className="mt-2">
|
||||
{customTools.map((tool, index) => (
|
||||
<div
|
||||
key={`custom-tool-${index}`}
|
||||
className={clsx(
|
||||
"flex items-center gap-2 px-3 py-2 rounded cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-800",
|
||||
selectedEntity?.type === "tool" && selectedEntity.name === tool.name && "bg-indigo-50 dark:bg-indigo-950/30"
|
||||
return (
|
||||
<>
|
||||
{/* Show composio cards - ordered by status */}
|
||||
{Object.values(composioTools)
|
||||
.map((card) => (
|
||||
<ComposioCard
|
||||
key={card.slug}
|
||||
card={card}
|
||||
selectedEntity={selectedEntity}
|
||||
onSelectTool={handleToolSelection}
|
||||
onDeleteTool={onDeleteTool}
|
||||
selectedRef={selectedRef}
|
||||
projectConfig={projectConfig}
|
||||
projectId={projectId}
|
||||
workflow={workflow}
|
||||
onProjectToolsUpdated={onProjectToolsUpdated}
|
||||
setSelectedToolkitSlug={setSelectedToolkitSlug}
|
||||
setShowToolsModal={setShowToolsModal}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Show MCP server cards */}
|
||||
{Object.entries(serverTools).map(([serverName, tools]) => (
|
||||
<ServerCard
|
||||
key={serverName}
|
||||
serverName={serverName}
|
||||
tools={tools}
|
||||
selectedEntity={selectedEntity}
|
||||
onSelectTool={handleToolSelection}
|
||||
onDeleteTool={onDeleteTool}
|
||||
selectedRef={selectedRef}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Show custom tools, including default library tools (read-only) */}
|
||||
{customTools.length > 0 && (
|
||||
<div className="mt-2">
|
||||
{customTools.map((tool, index) => (
|
||||
<div
|
||||
key={`custom-tool-${index}`}
|
||||
className={clsx(
|
||||
"flex items-center gap-2 px-3 py-2 rounded cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-800",
|
||||
selectedEntity?.type === "tool" && selectedEntity.name === tool.name && "bg-indigo-50 dark:bg-indigo-950/30",
|
||||
tool.isLibrary ? "cursor-default" : ""
|
||||
)}
|
||||
onClick={() => { if (!tool.isLibrary) handleToolSelection(tool.name); }}
|
||||
>
|
||||
{tool.isGeminiImage ? (
|
||||
<ImageIcon className="w-4 h-4 text-blue-600/70 dark:text-blue-500/70" />
|
||||
) : (
|
||||
<Boxes className="w-4 h-4 text-blue-600/70 dark:text-blue-500/70" />
|
||||
)}
|
||||
<span className={clsx(
|
||||
"flex-1 text-xs whitespace-normal break-words",
|
||||
// Match font styling to other tools even if read-only
|
||||
"text-zinc-900 dark:text-zinc-100"
|
||||
)}>{tool.name}</span>
|
||||
{tool.mockTool && (
|
||||
<span className="ml-2 px-1 py-0 rounded bg-purple-50 text-purple-400 dark:bg-purple-900/40 dark:text-purple-200 text-[11px] font-normal align-middle">Mocked</span>
|
||||
)}
|
||||
{!tool.isLibrary && (
|
||||
<Tooltip content="Remove tool" size="sm" delay={500}>
|
||||
<button
|
||||
className="ml-1 p-1 pr-2 rounded hover:bg-red-100 dark:hover:bg-red-900 flex items-center"
|
||||
onClick={e => { e.stopPropagation(); onDeleteTool(tool.name); }}
|
||||
>
|
||||
<Trash2 className="w-3 h-3 text-red-500" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
onClick={() => handleToolSelection(tool.name)}
|
||||
>
|
||||
<Boxes className="w-4 h-4 text-blue-600/70 dark:text-blue-500/70" />
|
||||
<span className="flex-1 text-xs text-zinc-900 dark:text-zinc-100 whitespace-normal break-words">{tool.name}</span>
|
||||
{tool.mockTool && (
|
||||
<span className="ml-2 px-1 py-0 rounded bg-purple-50 text-purple-400 dark:bg-purple-900/40 dark:text-purple-200 text-[11px] font-normal align-middle">Mocked</span>
|
||||
)}
|
||||
<Tooltip content="Remove tool" size="sm" delay={500}>
|
||||
<button
|
||||
className="ml-1 p-1 pr-2 rounded hover:bg-red-100 dark:hover:bg-red-900 flex items-center"
|
||||
onClick={e => { e.stopPropagation(); onDeleteTool(tool.name); }}
|
||||
>
|
||||
<Trash2 className="w-3 h-3 text-red-500" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
) : (
|
||||
<EmptyState
|
||||
entity="tools"
|
||||
hasFilteredItems={false}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EmptyState
|
||||
entity="tools"
|
||||
hasFilteredItems={false}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -1627,7 +1652,8 @@ const ComposioCard = ({
|
|||
<button
|
||||
className={clsx(
|
||||
"flex-1 flex items-center gap-2 text-sm text-left bg-transparent border-none p-0 m-0",
|
||||
tool.isLibrary ? "text-zinc-400 dark:text-zinc-600" : "text-zinc-900 dark:text-zinc-100"
|
||||
// Use same font styling for library tools; keep disabled state only
|
||||
"text-zinc-900 dark:text-zinc-100"
|
||||
)}
|
||||
onClick={() => onSelectTool(tool.name)}
|
||||
disabled={tool.isLibrary}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import { Panel } from "@/components/common/panel-common";
|
|||
import { Button as CustomButton } from "@/components/ui/button";
|
||||
|
||||
import { InputField } from "@/app/lib/components/input-field";
|
||||
import { getDefaultTools } from "@/app/lib/default_tools";
|
||||
import { VoiceSection } from "../config/components/voice";
|
||||
import { TopBar } from "./components/TopBar";
|
||||
|
||||
|
|
@ -2207,7 +2208,14 @@ export function WorkflowEditor({
|
|||
usedAgentNames={new Set(state.present.workflow.agents.filter((agent) => agent.name !== state.present.selection!.name).map((agent) => agent.name))}
|
||||
usedPipelineNames={new Set((state.present.workflow.pipelines || []).map((pipeline) => pipeline.name))}
|
||||
agents={state.present.workflow.agents}
|
||||
tools={state.present.workflow.tools}
|
||||
tools={(() => {
|
||||
const { tools } = state.present.workflow;
|
||||
const defaults = getDefaultTools();
|
||||
const map = new Map<string, any>();
|
||||
for (const t of tools) map.set(t.name, t);
|
||||
for (const t of defaults) if (!map.has(t.name)) map.set(t.name, t);
|
||||
return Array.from(map.values());
|
||||
})()}
|
||||
prompts={state.present.workflow.prompts}
|
||||
dataSources={dataSources}
|
||||
handleUpdate={(update) => { dispatchGuarded({ type: "update_agent", name: state.present.selection!.name, agent: update }); }}
|
||||
|
|
@ -2235,7 +2243,14 @@ export function WorkflowEditor({
|
|||
key={`overlay-${state.present.selection.name}-${configKey}`}
|
||||
prompt={state.present.workflow.prompts.find((prompt) => prompt.name === state.present.selection!.name)!}
|
||||
agents={state.present.workflow.agents}
|
||||
tools={state.present.workflow.tools}
|
||||
tools={(() => {
|
||||
const { tools } = state.present.workflow;
|
||||
const defaults = getDefaultTools();
|
||||
const map = new Map<string, any>();
|
||||
for (const t of tools) map.set(t.name, t);
|
||||
for (const t of defaults) if (!map.has(t.name)) map.set(t.name, t);
|
||||
return Array.from(map.values());
|
||||
})()}
|
||||
prompts={state.present.workflow.prompts}
|
||||
usedPromptNames={new Set(state.present.workflow.prompts.filter((prompt) => prompt.name !== state.present.selection!.name).map((prompt) => prompt.name))}
|
||||
handleUpdate={(update) => { dispatchGuarded({ type: "update_prompt", name: state.present.selection!.name, prompt: update }); }}
|
||||
|
|
@ -2313,7 +2328,14 @@ export function WorkflowEditor({
|
|||
usedAgentNames={new Set(state.present.workflow.agents.filter((agent) => agent.name !== state.present.selection!.name).map((agent) => agent.name))}
|
||||
usedPipelineNames={new Set((state.present.workflow.pipelines || []).map((pipeline) => pipeline.name))}
|
||||
agents={state.present.workflow.agents}
|
||||
tools={state.present.workflow.tools}
|
||||
tools={(() => {
|
||||
const { tools } = state.present.workflow;
|
||||
const defaults = getDefaultTools();
|
||||
const map = new Map<string, any>();
|
||||
for (const t of tools) map.set(t.name, t);
|
||||
for (const t of defaults) if (!map.has(t.name)) map.set(t.name, t);
|
||||
return Array.from(map.values());
|
||||
})()}
|
||||
prompts={state.present.workflow.prompts}
|
||||
dataSources={dataSources}
|
||||
handleUpdate={(update) => { dispatchGuarded({ type: "update_agent", name: state.present.selection!.name, agent: update }); }}
|
||||
|
|
@ -2341,7 +2363,14 @@ export function WorkflowEditor({
|
|||
key={`overlay2-${state.present.selection.name}-${configKey}`}
|
||||
prompt={state.present.workflow.prompts.find((prompt) => prompt.name === state.present.selection!.name)!}
|
||||
agents={state.present.workflow.agents}
|
||||
tools={state.present.workflow.tools}
|
||||
tools={(() => {
|
||||
const { tools } = state.present.workflow;
|
||||
const defaults = getDefaultTools();
|
||||
const map = new Map<string, any>();
|
||||
for (const t of tools) map.set(t.name, t);
|
||||
for (const t of defaults) if (!map.has(t.name)) map.set(t.name, t);
|
||||
return Array.from(map.values());
|
||||
})()}
|
||||
prompts={state.present.workflow.prompts}
|
||||
usedPromptNames={new Set(state.present.workflow.prompts.filter((prompt) => prompt.name !== state.present.selection!.name).map((prompt) => prompt.name))}
|
||||
handleUpdate={(update) => { dispatchGuarded({ type: "update_prompt", name: state.present.selection!.name, prompt: update }); }}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import crypto from "crypto";
|
|||
// Internal dependencies
|
||||
import { createTools, createRagTool } from "./agent-tools";
|
||||
import { ConnectedEntity, sanitizeTextWithMentions, Workflow, WorkflowAgent, WorkflowPipeline, WorkflowPrompt, WorkflowTool } from "@/app/lib/types/workflow_types";
|
||||
import { getDefaultTools } from "@/app/lib/default_tools";
|
||||
import { CHILD_TRANSFER_RELATED_INSTRUCTIONS, CONVERSATION_TYPE_INSTRUCTIONS, PIPELINE_TYPE_INSTRUCTIONS, RAG_INSTRUCTIONS, TASK_TYPE_INSTRUCTIONS, VARIABLES_CONTEXT_INSTRUCTIONS } from "./agent_instructions";
|
||||
import { PrefixLogger } from "@/app/lib/utils";
|
||||
import { Message, AssistantMessage, AssistantMessageWithToolCalls, ToolMessage } from "@/app/lib/types/types";
|
||||
|
|
@ -361,7 +362,15 @@ function mapConfig(workflow: z.infer<typeof Workflow>): {
|
|||
...acc,
|
||||
[agent.name]: agent
|
||||
}), {});
|
||||
const toolConfig: Record<string, z.infer<typeof WorkflowTool>> = workflow.tools.reduce((acc, tool) => ({
|
||||
// Merge workflow tools with default library tools (unique by name)
|
||||
const mergedTools = (() => {
|
||||
const defaults = getDefaultTools();
|
||||
const map = new Map<string, z.infer<typeof WorkflowTool>>();
|
||||
for (const t of workflow.tools) map.set(t.name, t);
|
||||
for (const t of defaults) if (!map.has(t.name)) map.set(t.name, t as any);
|
||||
return Array.from(map.values());
|
||||
})();
|
||||
const toolConfig: Record<string, z.infer<typeof WorkflowTool>> = mergedTools.reduce((acc, tool) => ({
|
||||
...acc,
|
||||
[tool.name]: tool
|
||||
}), {});
|
||||
|
|
|
|||
|
|
@ -95,29 +95,7 @@ export class CreateProjectUseCase implements ICreateProjectUseCase {
|
|||
}
|
||||
}
|
||||
|
||||
// Conditionally include Gemini Image tool by default if GOOGLE_API_KEY is present
|
||||
const hasGoogleKey = !!process.env.GOOGLE_API_KEY;
|
||||
const hasImageTool = (workflow.tools || []).some(t => t.isGeminiImage || t.name === 'Generate Image');
|
||||
if (hasGoogleKey && !hasImageTool) {
|
||||
const imageTool = {
|
||||
name: 'Generate Image',
|
||||
description: 'Generate an image using Google Gemini given a text prompt. Returns base64-encoded image data and any text parts.',
|
||||
isGeminiImage: true,
|
||||
parameters: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
prompt: { type: 'string', description: 'Text prompt describing the image to generate' },
|
||||
modelName: { type: 'string', description: 'Optional Gemini model override' },
|
||||
},
|
||||
required: ['prompt'],
|
||||
additionalProperties: true,
|
||||
},
|
||||
};
|
||||
workflow = {
|
||||
...workflow,
|
||||
tools: [...(workflow.tools || []), imageTool] as any,
|
||||
};
|
||||
}
|
||||
// Do not auto-attach image generation tool; it is available as a default library tool in the editor/runtime
|
||||
|
||||
// create project secret
|
||||
const secret = crypto.randomBytes(32).toString('hex');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue