Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export class OpenAIEmbeddingGenerator implements EmbeddingGenerator {
const api = new OpenAI({
apiKey: this.#settings.openAiKey,
organization: this.#settings.openAiOrganization,
baseURL: this.#settings.openAiEndpoint,
dangerouslyAllowBrowser: true, // It's fine in Rivet
});

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/model/nodes/ChatNodeBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import type { Inputs, Outputs } from '../GraphProcessor.js';
import { cleanHeaders, getInputOrData } from '../../utils/inputs.js';
import type { InternalProcessContext } from '../ProcessContext.js';
import { chatMessageToOpenAIChatCompletionMessage } from '../../utils/chatMessageToOpenAIChatCompletionMessage.js';
import { DEFAULT_CHAT_ENDPOINT } from '../../utils/defaults.js';
import type { TokenizerCallInfo } from '../../integrations/Tokenizer.js';
import { addWarning } from '../../utils/outputs.js';
import retry from 'p-retry';
Expand Down Expand Up @@ -1008,7 +1007,8 @@ export const ChatNodeBase = {
const isMultiResponse = data.useNumberOfChoicesInput || (data.numberOfChoices ?? 1) > 1;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[P0] Restore the default OpenAI URL when openAiEndpoint is blank. openAiEndpoint is normalized to '' by the processor/app settings, so ?? never falls back here. With the default configuration (no custom endpoint set), this now builds /chat/completions instead of https://api.openai.com/v1/chat/completions, and the same pattern in the new assistant/file/thread calls yields relative URLs as well. That breaks ordinary OpenAI usage for anyone who leaves the endpoint empty.

// Resolve to final endpoint if configured in ProcessContext

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[P1] Keep openAiEndpoint as a full chat endpoint here. The existing setting, docs, and presets all treat openAiEndpoint as the complete chat completions URL, for example LM Studio's http://localhost:1234/v1/chat/completions or Azure deployment URLs with ?api-version=.... Appending /chat/completions turns those into invalid URLs like .../chat/completions/chat/completions or appends the path after the query string, so custom chat endpoints stop working.

const configuredEndpoint = endpoint || context.settings.openAiEndpoint || DEFAULT_CHAT_ENDPOINT;
const baseUrl = context.settings.openAiEndpoint ?? 'https://api.openai.com/v1';
const configuredEndpoint = endpoint || `${baseUrl}/chat/completions`;
const resolvedEndpointAndHeaders = context.getChatNodeEndpoint
? await context.getChatNodeEndpoint(configuredEndpoint, finalModel)
: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ export const AttachAssistantFileNodeImpl: PluginNodeImpl<AttachAssistantFileNode
throw new Error('OpenAI key is not set.');
}

const url = `https://api.openai.com/v1/assistants/${assistantId}/files`;
const baseUrl = context.settings.openAiEndpoint ?? 'https://api.openai.com/v1';
const url = `${baseUrl}/assistants/${assistantId}/files`;

const response = await fetch(url, {
method: 'POST',
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/plugins/openai/nodes/CreateAssistantNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,9 +323,10 @@ export const CreateAssistantNodeImpl: PluginNodeImpl<CreateAssistantNode> = {
throw new Error('OpenAI key is not set.');
}

const baseUrl = context.settings.openAiEndpoint ?? 'https://api.openai.com/v1';
const url = assistantId.trim()
? `https://api.openai.com/v1/assistants/${assistantId}`
: 'https://api.openai.com/v1/assistants';
? `${baseUrl}/assistants/${assistantId}`
: `${baseUrl}/assistants`;

const response = await fetch(url, {
method: 'POST',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@ export const CreateThreadMessageNodeImpl: PluginNodeImpl<CreateThreadMessageNode
throw new Error('OpenAI key is not set.');
}

const url = `https://api.openai.com/v1/threads/${threadId}/messages`;
const baseUrl = context.settings.openAiEndpoint ?? 'https://api.openai.com/v1';
const url = `${baseUrl}/threads/${threadId}/messages`;

const response = await fetch(url, {
method: 'POST',
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/plugins/openai/nodes/CreateThreadNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ export const CreateThreadNodeImpl: PluginNodeImpl<CreateThreadNode> = {
return message as CreateMessageBody;
});

const url = threadId.trim() ? `https://api.openai.com/v1/threads/${threadId}` : 'https://api.openai.com/v1/threads';
const baseUrl = context.settings.openAiEndpoint ?? 'https://api.openai.com/v1';
const url = threadId.trim() ? `${baseUrl}/threads/${threadId}` : `${baseUrl}/threads`;

if (threadId && messages.length > 0) {
throw new Error('Cannot provide messages when modifying an existing thread.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ export const DeleteAssistantNodeImpl: PluginNodeImpl<DeleteAssistantNode> = {
throw new Error('OpenAI key is not set.');
}

const response = await fetch(`https://api.openai.com/v1/assistants/${assistantId}`, {
const baseUrl = context.settings.openAiEndpoint ?? 'https://api.openai.com/v1';
const response = await fetch(`${baseUrl}/assistants/${assistantId}`, {
method: 'DELETE',
headers: {
'OpenAI-Beta': 'assistants=v1',
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/plugins/openai/nodes/DeleteThreadNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ export const DeleteThreadNodeImpl: PluginNodeImpl<DeleteThreadNode> = {
throw new Error('OpenAI key is not set.');
}

const response = await fetch(`https://api.openai.com/v1/threads/${threadId}`, {
const baseUrl = context.settings.openAiEndpoint ?? 'https://api.openai.com/v1';
const response = await fetch(`${baseUrl}/threads/${threadId}`, {
method: 'DELETE',
headers: {
'OpenAI-Beta': 'assistants=v1',
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/plugins/openai/nodes/GetAssistantNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ export const GetAssistantNodeImpl: PluginNodeImpl<GetAssistantNode> = {
throw new Error('OpenAI key is not set.');
}

const response = await fetch(`https://api.openai.com/v1/assistants/${assistantId}`, {
const baseUrl = context.settings.openAiEndpoint ?? 'https://api.openai.com/v1';

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[P2] Do not reuse the chat endpoint as the assistants/files base URL. This is only safe when openAiEndpoint is an API root, but the setting is currently exposed as a Chat-node endpoint and is commonly configured to a full /chat/completions URL. In that case assistant/thread/file nodes now request paths like .../chat/completions/assistants/... instead of OpenAI's API, so enabling a custom chat endpoint breaks these nodes even if they should still talk to the default OpenAI service.

const response = await fetch(`${baseUrl}/assistants/${assistantId}`, {
method: 'GET',
headers: {
'OpenAI-Beta': 'assistants=v1',
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/plugins/openai/nodes/GetOpenAIFileNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ export const GetOpenAIFileNodeImpl: PluginNodeImpl<GetOpenAIFileNode> = {
throw new Error('OpenAI key is not set.');
}

const url = `https://api.openai.com/v1/files/${fileId}`;
const baseUrl = context.settings.openAiEndpoint ?? 'https://api.openai.com/v1';
const url = `${baseUrl}/files/${fileId}`;

const response = await fetch(url, {
method: 'GET',
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/plugins/openai/nodes/GetThreadNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ export const GetThreadNodeImpl: PluginNodeImpl<GetThreadNode> = {
throw new Error('OpenAI key is not set.');
}

const response = await fetch(`https://api.openai.com/v1/threads/${threadId}`, {
const baseUrl = context.settings.openAiEndpoint ?? 'https://api.openai.com/v1';
const response = await fetch(`${baseUrl}/threads/${threadId}`, {
method: 'GET',
headers: {
'OpenAI-Beta': 'assistants=v1',
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/plugins/openai/nodes/ListAssistantsNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ ${
}

const query = new URLSearchParams(queryParams);
const url = `https://api.openai.com/v1/assistants?${query.toString()}`;
const baseUrl = context.settings.openAiEndpoint ?? 'https://api.openai.com/v1';
const url = `${baseUrl}/assistants?${query.toString()}`;

const response = await fetch(url, {
method: 'GET',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ export const ListOpenAIFilesNodeImpl: PluginNodeImpl<ListOpenAIFilesNode> = {
throw new Error('OpenAI key is not set.');
}

const url = `https://api.openai.com/v1/files?purpose=${purpose}`;
const baseUrl = context.settings.openAiEndpoint ?? 'https://api.openai.com/v1';
const url = `${baseUrl}/files?purpose=${purpose}`;

const response = await fetch(url, {
method: 'GET',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,8 @@ ${
}

const query = new URLSearchParams(queryParams);
const url = `https://api.openai.com/v1/threads/${threadId}/messages?${query.toString()}`;
const baseUrl = context.settings.openAiEndpoint ?? 'https://api.openai.com/v1';
const url = `${baseUrl}/threads/${threadId}/messages?${query.toString()}`;

const response = await fetch(url, {
method: 'GET',
Expand Down
17 changes: 9 additions & 8 deletions packages/core/src/plugins/openai/nodes/RunThreadNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ export const RunThreadNodeImpl: PluginNodeImpl<RunThreadNode> = {
async process(data, inputData, context) {
const threadId = getInputOrData(data, inputData, 'threadId');
const assistantId = getInputOrData(data, inputData, 'assistantId');
const baseUrl = context.settings.openAiEndpoint ?? 'https://api.openai.com/v1';

let metadata = data.metadata.reduce(
(acc, { key, value }) => {
Expand Down Expand Up @@ -448,7 +449,7 @@ export const RunThreadNodeImpl: PluginNodeImpl<RunThreadNode> = {
? arrayizeDataValue(unwrapDataValue(messagesInput)).map((message) => coerceToThreadMessage(message))
: [];

response = await fetch('https://api.openai.com/v1/threads/runs', {
response = await fetch(`${baseUrl}/threads/runs`, {
method: 'POST',
headers: {
'OpenAI-Beta': 'assistants=v1',
Expand All @@ -465,7 +466,7 @@ export const RunThreadNodeImpl: PluginNodeImpl<RunThreadNode> = {
}),
});
} else {
response = await fetch(`https://api.openai.com/v1/threads/${threadId}/runs`, {
response = await fetch(`${baseUrl}/threads/${threadId}/runs`, {
method: 'POST',
headers: {
'OpenAI-Beta': 'assistants=v1',
Expand Down Expand Up @@ -509,7 +510,7 @@ export const RunThreadNodeImpl: PluginNodeImpl<RunThreadNode> = {
order: 'desc',
} satisfies OpenAIPaginationQuery);

const url = `https://api.openai.com/v1/threads/${body.thread_id}/runs/${body.id}/steps?${query.toString()}`;
const url = `${baseUrl}/threads/${body.thread_id}/runs/${body.id}/steps?${query.toString()}`;
const response = await fetch(url, {
method: 'GET',
headers: {
Expand Down Expand Up @@ -547,7 +548,7 @@ export const RunThreadNodeImpl: PluginNodeImpl<RunThreadNode> = {
const messageId = step.step_details.message_creation.message_id;

const messageResponse = await fetch(
`https://api.openai.com/v1/threads/${body.thread_id}/messages/${messageId}`,
`${baseUrl}/threads/${body.thread_id}/messages/${messageId}`,
{
method: 'GET',
headers: {
Expand Down Expand Up @@ -598,7 +599,7 @@ export const RunThreadNodeImpl: PluginNodeImpl<RunThreadNode> = {
latestBody.status === 'queued' ||
latestBody.status === 'requires_action'
) {
const runResponse = await fetch(`https://api.openai.com/v1/threads/${body.thread_id}/runs/${body.id}`, {
const runResponse = await fetch(`${baseUrl}/threads/${body.thread_id}/runs/${body.id}`, {
method: 'GET',
headers: {
'OpenAI-Beta': 'assistants=v1',
Expand Down Expand Up @@ -681,7 +682,7 @@ export const RunThreadNodeImpl: PluginNodeImpl<RunThreadNode> = {
}));

const submitResponse = await fetch(
`https://api.openai.com/v1/threads/${body.thread_id}/runs/${body.id}/submit_tool_outputs`,
`${baseUrl}/threads/${body.thread_id}/runs/${body.id}/submit_tool_outputs`,
{
method: 'POST',
headers: {
Expand Down Expand Up @@ -720,7 +721,7 @@ export const RunThreadNodeImpl: PluginNodeImpl<RunThreadNode> = {
});

const listStepsResponsePromise = await fetch(
`https://api.openai.com/v1/threads/${body.thread_id}/runs/${body.id}/steps?${listStepsQuery.toString()}`,
`${baseUrl}/threads/${body.thread_id}/runs/${body.id}/steps?${listStepsQuery.toString()}`,
{
method: 'GET',
headers: {
Expand All @@ -738,7 +739,7 @@ export const RunThreadNodeImpl: PluginNodeImpl<RunThreadNode> = {
} satisfies OpenAIPaginationQuery);

const messagesResponsePromise = await fetch(
`https://api.openai.com/v1/threads/${body.thread_id}/messages?${getMessagesQuery.toString()}`,
`${baseUrl}/threads/${body.thread_id}/messages?${getMessagesQuery.toString()}`,
{
method: 'GET',
headers: {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/plugins/openai/nodes/UploadFileNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ export const UploadFileNodeImpl: PluginNodeImpl<UploadFileNode> = {
formData.append('purpose', purpose);
formData.append('file', file);

const response = await fetch('https://api.openai.com/v1/files', {
const baseUrl = context.settings.openAiEndpoint ?? 'https://api.openai.com/v1';
const response = await fetch(`${baseUrl}/files`, {
method: 'POST',
headers: {
Authorization: `Bearer ${context.settings.openAiKey}`,
Expand Down
2 changes: 0 additions & 2 deletions packages/core/src/utils/defaults.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
export const DEFAULT_CHAT_ENDPOINT = 'https://api.openai.com/v1/chat/completions';

export const DEFAULT_CHAT_NODE_TIMEOUT = 30_000;
Loading