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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **LocalAI provider** (`SmartHopper.Providers.LocalAI`): new built-in provider for self-hosted [LocalAI](https://localai.io/) instances. Talks to the standard OpenAI-compatible `/v1/chat/completions` endpoint, with full support for tools/function calling, streaming, JSON Schema structured outputs, and an `Authorization: Bearer` API key when one is configured. Settings include a required **Base URL** (default `http://localhost:8080/v1`) plus optional API Key, Model, Streaming, Max Tokens, and Temperature. The provider does not auto-discover models — users list whatever models they have installed locally.
- **Ollama provider** (`SmartHopper.Providers.Ollama`): new built-in provider for [Ollama](https://ollama.com/) running locally via its [OpenAI-compatible endpoint](https://ollama.com/blog/openai-compatibility). Mirrors the LocalAI capabilities (tools, streaming, JSON Schema, optional bearer token for reverse-proxy setups) plus an extra `seed` extra-descriptor for reproducible sampling. Settings include a required **Base URL** (default `http://localhost:11434/v1`) plus optional API Key, Model (e.g. `llama3.1`, `qwen2.5:14b`, `mistral:7b-instruct` — install with `ollama pull <model>`), Streaming, Max Tokens, and Temperature.
- **DEV.md provider model sync automation**: added `tools/Update-DevProviderModels.ps1` and a GitHub workflow that validates provider model documentation on PRs and opens sync PRs after protected-branch provider registry updates.
- **README Trademark and Logo Usage Policy**: explicit policy clarifying that the SmartHopper name and logo are not licensed under LGPL, listing permitted uses (articles, tutorials, educational materials, references to the unmodified official plug-in) and uses requiring prior written permission (commercial bundling, forks, materials that may imply endorsement).

Expand Down
6 changes: 4 additions & 2 deletions DEV.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,8 @@ SmartHopper currently supports the following AI providers and features:
| Anthropic | ✅ Supported | Claude Console | Yes | No | No | Yes | Yes | Yes | No | ✅ Yes |
| OpenRouter | ✅ Supported | OpenRouter | No | No (varies by routed model) | No | Varies | Varies | Varies | Varies | ❌ No |
| Gemini | 🟠 Testing | Google AI Studio | Yes | Yes (thinking_level) | Yes | Yes | Yes | Yes | ✅ Yes | ✅ Yes |
| Ollama | ⚪ Planned | Local Ollama server | Planned | Planned | Planned | Planned | Planned | Planned | No | Planned |
| LocalAI | ⚪ Planned | LocalAI server | Planned | Planned | Planned | Planned | Planned | Planned | Planned | Planned |
| Ollama | 🟠 Testing | Local Ollama server (OpenAI-compatible) | Yes | Varies by model | Varies | Yes | Yes | Yes | No | ❌ No |
| LocalAI | 🟠 Testing | LocalAI server (OpenAI-compatible) | Yes | Varies by model | Varies | Yes | Yes | Yes | No | ❌ No |
| Black Forest Labs | ⚪ Planned | Black Forest Labs API | Planned | No | No | Planned | No | No | Planned | Planned |
| Stable Diffusion | ⚪ Planned | Local/API Stable Diffusion endpoint | Planned | No | No | Planned | No | No | Planned | Planned |

Expand All @@ -192,7 +192,9 @@ The following table summarizes the models explicitly registered as defaults or v
- `src/SmartHopper.Providers.Anthropic/AnthropicProviderModels.cs`
- `src/SmartHopper.Providers.DeepSeek/DeepSeekProviderModels.cs`
- `src/SmartHopper.Providers.Gemini/GeminiProviderModels.cs`
- `src/SmartHopper.Providers.LocalAI/LocalAIProviderModels.cs`
- `src/SmartHopper.Providers.MistralAI/MistralAIProviderModels.cs`
- `src/SmartHopper.Providers.Ollama/OllamaProviderModels.cs`
- `src/SmartHopper.Providers.OpenAI/OpenAIProviderModels.cs`
- `src/SmartHopper.Providers.OpenRouter/OpenRouterProviderModels.cs`

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ SmartHopper brings a context‑aware AI assistant and a suite of AI‑powered co
- ![Anthropic](src/SmartHopper.Providers.Anthropic/Resources/anthropic_icon.png) [Anthropic](https://anthropic.com/)
- ![OpenRouter](src/SmartHopper.Providers.OpenRouter/Resources/openrouter_icon.png) [OpenRouter](https://openrouter.ai/)
- ![Gemini](src/SmartHopper.Providers.Gemini/Resources/gemini_icon.png) [Google Gemini](https://ai.google.dev/)
- ![LocalAI](src/SmartHopper.Providers.LocalAI/Resources/localai_icon.png) [LocalAI](https://localai.io/) — self-hosted, OpenAI-compatible
- ![Ollama](src/SmartHopper.Providers.Ollama/Resources/ollama_icon.png) [Ollama](https://ollama.com/) — local, OpenAI-compatible

- Open Source — and it will always be.

Expand Down
28 changes: 28 additions & 0 deletions SmartHopper.sln
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartHopper.Core.Grasshoppe
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartHopper.Core.Tests", "src\SmartHopper.Core.Tests\SmartHopper.Core.Tests.csproj", "{C7E24C95-ADF6-4DBA-BDB7-73CFB1291053}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartHopper.Providers.LocalAI", "src\SmartHopper.Providers.LocalAI\SmartHopper.Providers.LocalAI.csproj", "{CBBB20C6-CE06-4EE7-A798-70CB83D88EB4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartHopper.Providers.Ollama", "src\SmartHopper.Providers.Ollama\SmartHopper.Providers.Ollama.csproj", "{E9280136-51DA-465A-8625-5E2C064D9946}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -224,6 +228,30 @@ Global
{C7E24C95-ADF6-4DBA-BDB7-73CFB1291053}.Release|x64.Build.0 = Release|Any CPU
{C7E24C95-ADF6-4DBA-BDB7-73CFB1291053}.Release|x86.ActiveCfg = Release|Any CPU
{C7E24C95-ADF6-4DBA-BDB7-73CFB1291053}.Release|x86.Build.0 = Release|Any CPU
{CBBB20C6-CE06-4EE7-A798-70CB83D88EB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CBBB20C6-CE06-4EE7-A798-70CB83D88EB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CBBB20C6-CE06-4EE7-A798-70CB83D88EB4}.Debug|x64.ActiveCfg = Debug|Any CPU
{CBBB20C6-CE06-4EE7-A798-70CB83D88EB4}.Debug|x64.Build.0 = Debug|Any CPU
{CBBB20C6-CE06-4EE7-A798-70CB83D88EB4}.Debug|x86.ActiveCfg = Debug|Any CPU
{CBBB20C6-CE06-4EE7-A798-70CB83D88EB4}.Debug|x86.Build.0 = Debug|Any CPU
{CBBB20C6-CE06-4EE7-A798-70CB83D88EB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CBBB20C6-CE06-4EE7-A798-70CB83D88EB4}.Release|Any CPU.Build.0 = Release|Any CPU
{CBBB20C6-CE06-4EE7-A798-70CB83D88EB4}.Release|x64.ActiveCfg = Release|Any CPU
{CBBB20C6-CE06-4EE7-A798-70CB83D88EB4}.Release|x64.Build.0 = Release|Any CPU
{CBBB20C6-CE06-4EE7-A798-70CB83D88EB4}.Release|x86.ActiveCfg = Release|Any CPU
{CBBB20C6-CE06-4EE7-A798-70CB83D88EB4}.Release|x86.Build.0 = Release|Any CPU
{E9280136-51DA-465A-8625-5E2C064D9946}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E9280136-51DA-465A-8625-5E2C064D9946}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9280136-51DA-465A-8625-5E2C064D9946}.Debug|x64.ActiveCfg = Debug|Any CPU
{E9280136-51DA-465A-8625-5E2C064D9946}.Debug|x64.Build.0 = Debug|Any CPU
{E9280136-51DA-465A-8625-5E2C064D9946}.Debug|x86.ActiveCfg = Debug|Any CPU
{E9280136-51DA-465A-8625-5E2C064D9946}.Debug|x86.Build.0 = Debug|Any CPU
{E9280136-51DA-465A-8625-5E2C064D9946}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9280136-51DA-465A-8625-5E2C064D9946}.Release|Any CPU.Build.0 = Release|Any CPU
{E9280136-51DA-465A-8625-5E2C064D9946}.Release|x64.ActiveCfg = Release|Any CPU
{E9280136-51DA-465A-8625-5E2C064D9946}.Release|x64.Build.0 = Release|Any CPU
{E9280136-51DA-465A-8625-5E2C064D9946}.Release|x86.ActiveCfg = Release|Any CPU
{E9280136-51DA-465A-8625-5E2C064D9946}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@
<ProjectReference Include="..\SmartHopper.Providers.Anthropic\SmartHopper.Providers.Anthropic.csproj" />
<ProjectReference Include="..\SmartHopper.Providers.DeepSeek\SmartHopper.Providers.DeepSeek.csproj" />
<ProjectReference Include="..\SmartHopper.Providers.Gemini\SmartHopper.Providers.Gemini.csproj" />
<ProjectReference Include="..\SmartHopper.Providers.LocalAI\SmartHopper.Providers.LocalAI.csproj" />
<ProjectReference Include="..\SmartHopper.Providers.MistralAI\SmartHopper.Providers.MistralAI.csproj" />
<ProjectReference Include="..\SmartHopper.Providers.Ollama\SmartHopper.Providers.Ollama.csproj" />
<ProjectReference Include="..\SmartHopper.Providers.OpenAI\SmartHopper.Providers.OpenAI.csproj" />
<ProjectReference Include="..\SmartHopper.Providers.OpenRouter\SmartHopper.Providers.OpenRouter.csproj" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@
<!-- Built-in providers -->
<InternalsVisibleTo Include="SmartHopper.Providers.Anthropic, PublicKey=$(SmartHopperPublicKey)" />
<InternalsVisibleTo Include="SmartHopper.Providers.DeepSeek, PublicKey=$(SmartHopperPublicKey)" />
<InternalsVisibleTo Include="SmartHopper.Providers.LocalAI, PublicKey=$(SmartHopperPublicKey)" />
<InternalsVisibleTo Include="SmartHopper.Providers.MistralAI, PublicKey=$(SmartHopperPublicKey)" />
<InternalsVisibleTo Include="SmartHopper.Providers.Ollama, PublicKey=$(SmartHopperPublicKey)" />
<InternalsVisibleTo Include="SmartHopper.Providers.OpenAI, PublicKey=$(SmartHopperPublicKey)" />
<InternalsVisibleTo Include="SmartHopper.Providers.OpenRouter, PublicKey=$(SmartHopperPublicKey)" />
<InternalsVisibleTo Include="SmartHopper.Providers.Gemini, PublicKey=$(SmartHopperPublicKey)" />
Expand Down
91 changes: 91 additions & 0 deletions src/SmartHopper.Providers.LocalAI/LocalAIJsonSchemaAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* SmartHopper - AI-powered Grasshopper Plugin
* Copyright (C) 2024-2026 Marc Roca Musach
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
*/

using System;
using Newtonsoft.Json.Linq;
using SmartHopper.Infrastructure.AICall.JsonSchemas;

namespace SmartHopper.Providers.LocalAI
{
/// <summary>
/// JSON schema adapter for LocalAI.
/// LocalAI follows the OpenAI schema requirements (object-root) when used with
/// the llama.cpp grammar-based JSON output. Non-object schemas are wrapped.
/// </summary>
internal sealed class LocalAIJsonSchemaAdapter : IJsonSchemaAdapter
{
/// <inheritdoc/>
public string ProviderName => LocalAIProvider.NameValue;

/// <inheritdoc/>
public (JObject wrapped, SchemaWrapperInfo info) Wrap(JObject schema)
{
if (schema is null)
{
throw new ArgumentNullException(nameof(schema));
}

var schemaType = schema["type"]?.ToString();

if (string.Equals(schemaType, "object", StringComparison.OrdinalIgnoreCase))
{
return (schema, new SchemaWrapperInfo { IsWrapped = false, ProviderName = this.ProviderName });
}

if (string.Equals(schemaType, "array", StringComparison.OrdinalIgnoreCase))
{
var wrapped = new JObject
{
["type"] = "object",
["properties"] = new JObject { ["items"] = schema },
["required"] = new JArray { "items" },
["additionalProperties"] = false,
};
return (wrapped, new SchemaWrapperInfo { IsWrapped = true, WrapperType = "array", PropertyName = "items", ProviderName = this.ProviderName });
}

if (schemaType == "string" || schemaType == "number" || schemaType == "integer" || schemaType == "boolean")
{
var wrapped = new JObject
{
["type"] = "object",
["properties"] = new JObject { ["value"] = schema },
["required"] = new JArray { "value" },
["additionalProperties"] = false,
};
return (wrapped, new SchemaWrapperInfo { IsWrapped = true, WrapperType = schemaType ?? "primitive", PropertyName = "value", ProviderName = this.ProviderName });
}

var generic = new JObject
{
["type"] = "object",
["properties"] = new JObject { ["data"] = schema },
["required"] = new JArray { "data" },
["additionalProperties"] = false,
};
return (generic, new SchemaWrapperInfo { IsWrapped = true, WrapperType = "unknown", PropertyName = "data", ProviderName = this.ProviderName });
}

/// <inheritdoc/>
public string Unwrap(string content, SchemaWrapperInfo info)
{
// Default service logic handles unwrap for object-root payloads
return content;
}
}
}
Loading
Loading