Skip to content
Merged

1570 #5325

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f065550
1570 Unify copilot connection listing behind CopilotConnectionLister
ivicac Jul 2, 2026
9e24755
1570 Allow property-options picker tools in embedded chat
ivicac Jul 2, 2026
6f69922
1570 Config-aware output-reference validation
ivicac Jul 2, 2026
a709496
1570 Propagate caller identity to all Copilot agent tools to fix tool…
ivicac Jul 2, 2026
0bf9785
1570 Auto-resolve unambiguous component slug in listConnectionsForCom…
ivicac Jul 2, 2026
828864c
1570 Workflow can have multiple triggers
ivicac Jul 2, 2026
4634057
1570 Workflow can have multiple triggers
ivicac Jun 28, 2026
759e1f1
1570 Centralize AI provider/model/key resolution in AiProviderFacade
ivicac Jul 2, 2026
87e807a
1570 Rehydrate EnvironmentContext on tool-execution worker threads
ivicac Jul 2, 2026
233ad63
1652 Add Ollama embedding model support
ivicac Jul 2, 2026
419d1fd
1570 Embed Copilot docs with a dedicated internal OpenAI key
ivicac Jul 2, 2026
94f9bfd
1570 Add Ollama embedding provider support
ivicac Jul 2, 2026
c9d1b0a
1570 Address PR review: restore prior environment binding and select …
ivicac Jul 3, 2026
1089557
1570 Fail fast when Copilot docs OpenAI embedding api-key is missing
ivicac Jul 3, 2026
2d84b96
1570 Rename
ivicac Jul 3, 2026
ba7062b
1570 Support a configurable Ollama base URL in the AI Providers catalog
ivicac Jul 3, 2026
08d08d4
1570 Generate
ivicac Jul 3, 2026
f3e2f17
1570 client - Add Ollama Base URL field to AI Providers settings
ivicac Jul 3, 2026
4a1a296
1570 Replace inline Ollama key check with Provider.requiresApiKey()
ivicac Jul 3, 2026
9fae64c
1570 Address PR review: null-safe Ollama response format and quieter …
ivicac Jul 3, 2026
6a3306b
1570 client - Fix stray backtick in AI provider API key description
ivicac Jul 3, 2026
00dd784
1570 Update environment variables doc with Ollama, Copilot docs embed…
ivicac Jul 3, 2026
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Button from '@/components/Button/Button';
import {Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage} from '@/components/ui/form';
import {Input} from '@/components/ui/input';
import {AiProvider} from '@/ee/shared/middleware/platform/configuration';
import {useUpdateAiProviderMutation} from '@/ee/shared/mutations/platform/aiProvider.mutations';
import {AiProviderKeys} from '@/ee/shared/queries/platform/aiProviders.queries';
import {WorkflowNodeOptionKeys} from '@/shared/queries/platform/workflowNodeOptions.queries';
Expand All @@ -9,26 +10,28 @@ import {useQueryClient} from '@tanstack/react-query';
import {useForm} from 'react-hook-form';
import {z} from 'zod';

const formSchema = z.object({
apiKey: z.string().min(1, {
message: 'API Key is required.',
}),
});

const AiProviderForm = ({
aiProvider,
environment,
id,
onClose,
showCancel = false,
}: {
aiProvider: AiProvider;
environment: number;
id: number;
onClose: () => void;
showCancel: boolean;
}) => {
const isOllama = aiProvider.name?.toLowerCase() === 'ollama';

const formSchema = z.object({
apiKey: isOllama ? z.string().optional() : z.string().min(1, {message: 'API Key is required.'}),
url: z.string().optional(),
});

const form = useForm<z.infer<typeof formSchema>>({
defaultValues: {
apiKey: '',
url: aiProvider.url ?? '',
},
resolver: zodResolver(formSchema),
});
Expand All @@ -51,33 +54,53 @@ const AiProviderForm = ({
function handleSubmit(values: z.infer<typeof formSchema>) {
updateAiProviderMutation.mutate({
environment,
id,
updateAiProviderRequest: {
apiKey: values.apiKey,
},
id: aiProvider.id!,
updateAiProviderRequest: isOllama ? {url: values.url} : {apiKey: values.apiKey},
});
}

return (
<Form {...form}>
<form className="space-y-4" onSubmit={form.handleSubmit(handleSubmit)}>
<FormField
control={form.control}
name="apiKey"
render={({field}) => (
<FormItem>
<FormLabel>API Key</FormLabel>
{isOllama ? (
<FormField
control={form.control}
name="url"
render={({field}) => (
<FormItem>
<FormLabel>Base URL</FormLabel>

<FormControl>
<Input placeholder="API Key" {...field} />
</FormControl>
<FormControl>
<Input placeholder="http://localhost:11434" {...field} />
</FormControl>

<FormDescription>This is your AI provider&apos;`s API key.</FormDescription>
<FormDescription>
The base URL of your Ollama server. Leave blank to use http://localhost:11434.
</FormDescription>

<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
) : (
<FormField
control={form.control}
name="apiKey"
render={({field}) => (
<FormItem>
<FormLabel>API Key</FormLabel>

<FormControl>
<Input placeholder="API Key" {...field} />
</FormControl>

<FormDescription>This is your AI provider&apos;s API key.</FormDescription>

<FormMessage />
</FormItem>
)}
/>
)}

<div className="flex gap-1">
{showCancel && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {AiProvider} from '@/ee/shared/middleware/platform/configuration';
import {createTestQueryClientWrapper} from '@/shared/util/test-utils';
import {render, screen} from '@testing-library/react';
import {fireEvent, render, screen} from '@testing-library/react';
import {ReactNode} from 'react';
import {describe, expect, it, vi} from 'vitest';

Expand Down Expand Up @@ -75,6 +75,45 @@ describe('AiProviderList', () => {
expect(screen.getByText('Anthropic')).toBeInTheDocument();
});

it('shows the Base URL row with the localhost default for Ollama when no URL is set', async () => {
const ollamaProviders: AiProvider[] = [
{
enabled: false,
icon: '/icons/ollama.svg',
id: 3,
name: 'Ollama',
supportsEmbeddings: true,
},
];

renderWithProviders(<AiProviderList aiProviders={ollamaProviders} environment={1} />);

fireEvent.click(screen.getByText('Ollama'));

expect(await screen.findByText('Base URL:')).toBeInTheDocument();
expect(screen.getByText('http://localhost:11434')).toBeInTheDocument();
});

it('shows the configured Base URL for Ollama', async () => {
const ollamaProviders: AiProvider[] = [
{
enabled: true,
icon: '/icons/ollama.svg',
id: 3,
name: 'Ollama',
supportsEmbeddings: true,
url: 'http://remote-host:11434',
},
];

renderWithProviders(<AiProviderList aiProviders={ollamaProviders} environment={1} />);

fireEvent.click(screen.getByText('Ollama'));

expect(await screen.findByText('Base URL:')).toBeInTheDocument();
expect(screen.getByText('http://remote-host:11434')).toBeInTheDocument();
});

it('does not render the Embeddings badge when no provider supports embeddings', () => {
const noEmbeddingProviders: AiProvider[] = [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import InlineSVG from 'react-inlinesvg';

import './AiProviderList.css';

const isOllamaProvider = (aiProvider: AiProvider) => aiProvider.name?.toLowerCase() === 'ollama';

// Ollama runs locally and needs no API key, so it counts as configured; other providers require an API key.
const isConfigured = (aiProvider: AiProvider) => isOllamaProvider(aiProvider) || !!aiProvider.apiKey;

const AiProviderList = ({aiProviders, environment}: {aiProviders: AiProvider[]; environment: number}) => {
const [enabledItems, setEnabledItems] = useState<{[key: number]: boolean}>({});
const [openItem, setOpenItem] = useState<string>();
Expand All @@ -39,11 +44,13 @@ const AiProviderList = ({aiProviders, environment}: {aiProviders: AiProvider[];

setOpenItem(undefined);

if (!aiProvider.apiKey && value) {
const configured = isConfigured(aiProvider);

if (!configured && value) {
setOpenItem(`item-${aiProvider.id}`);
}

if (aiProvider.apiKey) {
if (configured) {
enableAiProviderMutation.mutate({
enable: value,
environment,
Expand All @@ -58,7 +65,7 @@ const AiProviderList = ({aiProviders, environment}: {aiProviders: AiProvider[];

aiProviders.forEach((aiProvider) => {
enabledItems[aiProvider.id!] = !!aiProvider.enabled;
showForm[aiProvider.id!] = !aiProvider.apiKey;
showForm[aiProvider.id!] = !isConfigured(aiProvider);
});

setEnabledItems(enabledItems);
Expand Down Expand Up @@ -112,21 +119,27 @@ const AiProviderList = ({aiProviders, environment}: {aiProviders: AiProvider[];
<AccordionContent className="pb-3 pl-9">
{showForm[aiProvider.id!] ? (
<AiProviderForm
aiProvider={aiProvider}
environment={environment}
id={aiProvider.id!}
onClose={() =>
setShowForm((prev) => ({
...prev,
[aiProvider.id!]: false,
}))
}
showCancel={!!aiProvider.apiKey}
showCancel={isConfigured(aiProvider)}
/>
) : (
<div className="flex items-center gap-2">
<span className="text-base text-muted-foreground">API Key: </span>
<span className="text-base text-muted-foreground">
{isOllamaProvider(aiProvider) ? 'Base URL: ' : 'API Key: '}
</span>

<span className="text-base">{aiProvider.apiKey}</span>
<span className="text-base">
{isOllamaProvider(aiProvider)
? aiProvider.url || 'http://localhost:11434'
: aiProvider.apiKey}
</span>

<Button
onClick={() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Name | Type
`name` | string
`icon` | string
`apiKey` | string
`url` | string
`enabled` | boolean
`supportsEmbeddings` | boolean

Expand All @@ -25,6 +26,7 @@ const example = {
"name": null,
"icon": null,
"apiKey": null,
"url": null,
"enabled": null,
"supportsEmbeddings": null,
} satisfies AiProvider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
Name | Type
------------ | -------------
`apiKey` | string
`url` | string

## Example

Expand All @@ -16,6 +17,7 @@ import type { UpdateAiProviderRequest } from ''
// TODO: Update the object below with actual values
const example = {
"apiKey": null,
"url": null,
} satisfies UpdateAiProviderRequest

console.log(example)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ export interface AiProvider {
* @memberof AiProvider
*/
apiKey?: string;
/**
* The base URL of an AI provider (used by Ollama; blank defaults to localhost).
* @type {string}
* @memberof AiProvider
*/
url?: string;
/**
* The enabled status of an AI provider.
* @type {boolean}
Expand Down Expand Up @@ -79,6 +85,7 @@ export function AiProviderFromJSONTyped(json: any, ignoreDiscriminator: boolean)
'name': json['name'],
'icon': json['icon'] == null ? undefined : json['icon'],
'apiKey': json['apiKey'] == null ? undefined : json['apiKey'],
'url': json['url'] == null ? undefined : json['url'],
'enabled': json['enabled'] == null ? undefined : json['enabled'],
'supportsEmbeddings': json['supportsEmbeddings'] == null ? undefined : json['supportsEmbeddings'],
};
Expand All @@ -96,6 +103,7 @@ export function AiProviderToJSONTyped(value?: Omit<AiProvider, 'id'|'name'|'icon
return {

'apiKey': value['apiKey'],
'url': value['url'],
'enabled': value['enabled'],
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export interface UpdateAiProviderRequest {
* @memberof UpdateAiProviderRequest
*/
apiKey?: string;
/**
* The base URL of an AI provider (used by Ollama; blank defaults to localhost).
* @type {string}
* @memberof UpdateAiProviderRequest
*/
url?: string;
}

/**
Expand All @@ -45,6 +51,7 @@ export function UpdateAiProviderRequestFromJSONTyped(json: any, ignoreDiscrimina
return {

'apiKey': json['apiKey'] == null ? undefined : json['apiKey'],
'url': json['url'] == null ? undefined : json['url'],
};
}

Expand All @@ -60,6 +67,7 @@ export function UpdateAiProviderRequestToJSONTyped(value?: UpdateAiProviderReque
return {

'apiKey': value['apiKey'],
'url': value['url'],
};
}

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ ByteChef can be configured using environment variables. This page documents all
| Environment Variable | Description | Default Value |
|---|---|---|
| `BYTECHEF_AI_COPILOT_ENABLED` | Enable or disable the AI copilot feature | `false` |
| `BYTECHEF_AI_COPILOT_PROVIDER` | The AI provider to use for copilot (OPENAI, ANTHROPIC) | `ANTHROPIC` |
| `BYTECHEF_AI_COPILOT_DOCS_EMBEDDING_PROVIDER` | Embedding provider for the Copilot documentation index (OLLAMA, OPENAI) | - |
| `BYTECHEF_AI_COPILOT_DOCS_EMBEDDING_APIKEY` | API key for the Copilot documentation embedding provider — OpenAI only; Ollama runs locally and needs none (sensitive) | - |

## AI Firecrawl Configuration

Expand Down Expand Up @@ -41,6 +42,11 @@ ByteChef can be configured using environment variables. This page documents all
| Environment Variable | Description | Default Value |
|---|---|---|
| `BYTECHEF_AI_MEMORY_PROVIDER` | Memory storage provider for chat-style interactions (AWS, IN_MEMORY, JDBC, REDIS) | `JDBC` |
| `BYTECHEF_AI_MEMORY_AWS_BUCKETPREFIX` | Prefix used to derive the per-tenant S3 bucket name (provider `AWS`) | `bytechef-chat-memory` |
| `BYTECHEF_AI_MEMORY_AWS_REGION` | AWS region for S3-backed chat memory (provider `AWS`) | - |
| `BYTECHEF_AI_MEMORY_AWS_ACCESSKEYID` | AWS access key ID for S3-backed chat memory (sensitive) | - |
| `BYTECHEF_AI_MEMORY_AWS_SECRETACCESSKEY` | AWS secret access key for S3-backed chat memory (sensitive) | - |
| `BYTECHEF_AI_MEMORY_AWS_KEYPREFIX` | Key prefix prepended to every stored object key (provider `AWS`) | - |

## AI Provider API Keys

Expand All @@ -52,6 +58,8 @@ ByteChef can be configured using environment variables. This page documents all
| `BYTECHEF_AI_PROVIDER_GROQ_APIKEY` | Groq API key (sensitive) | - |
| `BYTECHEF_AI_PROVIDER_MISTRAL_APIKEY` | Mistral API key (sensitive) | - |
| `BYTECHEF_AI_PROVIDER_NVIDIA_APIKEY` | NVIDIA API key (sensitive) | - |
| `BYTECHEF_AI_PROVIDER_OLLAMA_APIKEY` | Ollama API key (sensitive) | - |
| `BYTECHEF_AI_PROVIDER_OLLAMA_URL` | Ollama server base URL; fallback for chat and embedding models (defaults to `http://localhost:11434` when blank) | - |
| `BYTECHEF_AI_PROVIDER_OPENAI_APIKEY` | OpenAI API key (sensitive) | - |
| `BYTECHEF_AI_PROVIDER_PERPLEXITY_APIKEY` | Perplexity API key (sensitive) | - |
| `BYTECHEF_AI_PROVIDER_STABILITY_APIKEY` | Stability API key (sensitive) | - |
Expand All @@ -72,6 +80,7 @@ ByteChef can be configured using environment variables. This page documents all

| Environment Variable | Description | Default Value |
|---|---|---|
| `BYTECHEF_AI_PROVIDER_EMBEDDING_OLLAMA_OPTIONS_MODEL` | Ollama embedding model name | `qwen3-embedding:8b` |
| `BYTECHEF_AI_PROVIDER_EMBEDDING_OPENAI_OPTIONS_MODEL` | OpenAI embedding model name | `text-embedding-3-small` |

## AI Vectorstore Configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ bytechef:
reasoning-effect: medium
verbosity: low
embedding:
ollama:
options:
model: qwen3-embedding:8b
openai:
options:
model: text-embedding-3-small
Expand Down
Loading
Loading