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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ OPENROUTERAPIKEY='<add_your_openrouter_api_key_here>'
# If you want to use IO.net, add your key here
IONETAPIKEY='<add_your_ionet_api_key_here>'

# If you want to use Morpheus decentralized AI, add your key here
MORPHEUS_API_KEY='<add_your_morpheus_api_key_here>'

########################################
# API Rate Limiting Configuration
RATE_LIMIT_ENABLED='false' # Enable/disable API key rate limiting
Expand Down
23 changes: 22 additions & 1 deletion backend/node/create_nodes/providers_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
"anthropic",
"xai",
"google",
"openrouter"
"openrouter",
"ionet",
"morpheus"
]
},
"model": {
Expand Down Expand Up @@ -216,6 +218,25 @@
}
}
},
{
"if": {
"properties": {
"provider": { "const": "morpheus" }
}
},
"then": {
"x-models-url": "https://api.mor.org/api/v1/models",
"x-plugin-config-defaults": {
"api_key_env_var": "MORPHEUS_API_KEY",
"api_url": "https://api.mor.org/api/v1"
},
"properties": {
"plugin": {
"const": "openai-compatible"
}
}
}
},
{
"if": {
"properties": {
Expand Down
79 changes: 78 additions & 1 deletion frontend/src/assets/schemas/providers_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
"openai",
"anthropic",
"xai",
"google"
"google",
"openrouter",
"ionet",
"morpheus"
]
},
"model": {
Expand Down Expand Up @@ -102,6 +105,61 @@
}
}
},
{
"if": {
"properties": {
"provider": { "const": "openrouter" }
}
},
"then": {
"properties": {
"plugin": {
"const": "openai-compatible"
},
"model": {
"enum": [
"@preset/rally-testnet-gpt-5-1",
"@preset/rally-testnet-sonnet-4-5",
"@preset/rally-testnet-gemini-3-flash",
"openai/gpt-5.2",
"openai/gpt-5.1",
"openai/gpt-5-mini",
"z-ai/glm-4.7",
"moonshotai/kimi-k2-0905",
"moonshotai/kimi-k2.5",
"deepseek/deepseek-v3.2",
"qwen/qwen3-235b-a22b-2507",
"anthropic/claude-sonnet-4.5",
"google/gemini-3-flash-preview",
"x-ai/grok-4",
"mistralai/mistral-large-2512",
"meta-llama/llama-4-maverick",
"nvidia/llama-3.1-nemotron-ultra-253b-v1"
]
}
}
}
},
{
"if": {
"properties": {
"provider": { "const": "ionet" }
}
},
"then": {
"properties": {
"plugin": {
"const": "openai-compatible"
},
"model": {
"enum": [
"deepseek-ai/DeepSeek-V3.2",
"meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8"
]
}
}
}
},
{
"if": {
"properties": {
Expand Down Expand Up @@ -150,6 +208,25 @@
}
}
},
{
"if": {
"properties": {
"provider": { "const": "morpheus" }
}
},
"then": {
"x-models-url": "https://api.mor.org/api/v1/models",
"x-plugin-config-defaults": {
"api_key_env_var": "MORPHEUS_API_KEY",
"api_url": "https://api.mor.org/api/v1"
},
"properties": {
"plugin": {
"const": "openai-compatible"
}
}
}
},
{
"if": {
"properties": {
Expand Down
58 changes: 56 additions & 2 deletions frontend/src/components/Simulator/ProviderModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ const pluginOptions = ref<string[]>([]);
const modelOptions = ref<string[]>([]);
const isPluginLocked = ref(false);
const customProvider = ref(false);
const isLoadingModels = ref(false);
const modelFetchError = ref('');
const providerOptions = ref<string[]>([]);

const availablePluginOptions = computed(() => {
Expand Down Expand Up @@ -201,6 +203,30 @@ function extractDefaults(
return defaults;
}

const fetchDynamicModels = async (modelsUrl: string) => {
isLoadingModels.value = true;
modelFetchError.value = '';
try {
const response = await fetch(modelsUrl);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
if (data?.data && Array.isArray(data.data)) {
modelOptions.value = data.data
.filter((m: any) => !m.modelType || m.modelType === 'LLM')
.map((m: any) => m.id);
if (modelOptions.value.length > 0 && isCreateMode.value) {
newProviderData.model = modelOptions.value[0];
}
}
} catch (err) {
console.error('Failed to fetch models:', err);
modelFetchError.value =
'Could not fetch available models. You can type a model name manually.';
} finally {
isLoadingModels.value = false;
}
};

const pluginConfigProperties = ref<Record<string, any>>({});
const configProperties = ref<Record<string, any>>({});

Expand Down Expand Up @@ -259,6 +285,27 @@ const checkRules = () => {
}
}
});

// Provider-specific post-processing (x-models-url, x-plugin-config-defaults)
schema.allOf.forEach((rule: any) => {
if (rule.if?.properties?.provider?.const === newProviderData.provider) {
// Override plugin_config with provider-specific defaults
if (rule.then?.['x-plugin-config-defaults'] && isCreateMode.value) {
Object.assign(
newProviderData.plugin_config,
rule.then['x-plugin-config-defaults'],
);
}
// Fetch models dynamically for providers with x-models-url
if (
rule.then?.['x-models-url'] &&
modelOptions.value.length === 0 &&
isCreateMode.value
) {
fetchDynamicModels(rule.then['x-models-url']);
}
}
});
};

const toggleCustomProvider = () => {
Expand Down Expand Up @@ -390,8 +437,15 @@ const configurationError = computed(() => {

<MoreInfo text="The name of the model." />
</FieldLabel>

<div v-if="isLoadingModels" class="py-2 text-sm opacity-50">
Loading available models...
</div>

<Alert warning v-if="modelFetchError">{{ modelFetchError }}</Alert>

<TextInput
v-if="modelOptions.length === 0"
v-if="modelOptions.length === 0 && !isLoadingModels"
id="model"
name="model"
v-model="newProviderData.model"
Expand All @@ -400,7 +454,7 @@ const configurationError = computed(() => {
placeholder="i.e. my-model"
/>
<SelectInput
v-if="modelOptions.length > 0"
v-if="modelOptions.length > 0 && !isLoadingModels"
id="plugin"
name="plugin"
v-model="newProviderData.model"
Expand Down