-
Notifications
You must be signed in to change notification settings - Fork 22
Add OpenPaths provider #244
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| /** | ||
| * @license | ||
| * Copyright 2025 Autohand AI LLC | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| import { OpenRouterClient } from "./OpenRouterClient.js"; | ||
| import type { LLMProvider } from "./LLMProvider.js"; | ||
| import type { | ||
| LLMRequest, | ||
| LLMResponse, | ||
| OpenPathsSettings, | ||
| NetworkSettings, | ||
| } from "../types.js"; | ||
|
|
||
| const OPENPATHS_BASE_URL = "https://openpaths.io/v1"; | ||
| const OPENPATHS_MODELS_URL = "https://openpaths.io/v1/models"; | ||
|
|
||
| /** | ||
| * OpenPaths is an OpenAI-compatible model gateway (https://openpaths.io). | ||
| * It reuses the generic OpenAI-compatible client used by OpenRouter, pointed | ||
| * at the OpenPaths base URL. | ||
| */ | ||
| export class OpenPathsProvider implements LLMProvider { | ||
| private client: OpenRouterClient; | ||
| private model: string; | ||
|
|
||
| constructor(config: OpenPathsSettings, networkSettings?: NetworkSettings) { | ||
| this.client = new OpenRouterClient( | ||
| { ...config, baseUrl: config.baseUrl ?? OPENPATHS_BASE_URL }, | ||
| networkSettings, | ||
| ); | ||
| this.model = config.model; | ||
| } | ||
|
|
||
| getName(): string { | ||
| return "openpaths"; | ||
| } | ||
|
|
||
| setModel(model: string): void { | ||
| this.model = model; | ||
| this.client.setDefaultModel(model); | ||
| } | ||
|
|
||
| async listModels(): Promise<string[]> { | ||
| try { | ||
| const response = await fetch(OPENPATHS_MODELS_URL, { | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| signal: AbortSignal.timeout(10000), | ||
| }); | ||
|
|
||
| if (response.ok) { | ||
| const data = (await response.json()) as { data?: Array<{ id?: string }> }; | ||
| const ids = (Array.isArray(data?.data) ? data.data : []) | ||
| .map((model) => model.id) | ||
| .filter((id): id is string => Boolean(id)); | ||
|
|
||
| if (ids.length > 0) { | ||
| return ids; | ||
| } | ||
| } | ||
| } catch { | ||
| // Fall through to the static fallback list below. | ||
| } | ||
|
|
||
| return [ | ||
| "openpaths/auto", | ||
| "openpaths/auto-code", | ||
| "openpaths/auto-fast", | ||
| "openpaths/auto-reasoning", | ||
| ]; | ||
| } | ||
|
|
||
| async isAvailable(): Promise<boolean> { | ||
| // For OpenPaths, we can't easily check without making a request | ||
| // Return true if we have an API key | ||
| return true; | ||
| } | ||
|
|
||
| async complete(request: LLMRequest): Promise<LLMResponse> { | ||
| return this.client.complete(request); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ import { OllamaProvider } from './OllamaProvider.js'; | |
| import { OpenAIProvider } from './OpenAIProvider.js'; | ||
| import { LlamaCppProvider } from './LlamaCppProvider.js'; | ||
| import { OpenRouterProvider } from './OpenRouterProvider.js'; | ||
| import { OpenPathsProvider } from './OpenPathsProvider.js'; | ||
| import { MLXProvider } from './MLXProvider.js'; | ||
| import { LLMGatewayProvider } from './LLMGatewayProvider.js'; | ||
| import { AzureProvider } from './AzureProvider.js'; | ||
|
|
@@ -154,6 +155,12 @@ export class ProviderFactory { | |
| } | ||
| return new BedrockProvider(config.bedrock); | ||
|
|
||
| case 'openpaths': | ||
| if (!config.openpaths) { | ||
| return new UnconfiguredProvider('openpaths'); | ||
| } | ||
| return new OpenPathsProvider(config.openpaths, config.network); | ||
|
|
||
| case 'openrouter': | ||
| default: | ||
| if (!config.openrouter) { | ||
|
|
@@ -169,7 +176,7 @@ export class ProviderFactory { | |
| */ | ||
| static getProviderNames(config?: Pick<AutohandConfig, 'features'> | null): ProviderName[] { | ||
| // Sorted DESC by display name: Z.ai, xAI, Vertex AI, NVIDIA, OpenRouter, OpenAI, Ollama, MLX, LLM Gateway, llama.cpp, DeepSeek, Cerebras, Bedrock, Azure | ||
| const providers: ProviderName[] = ['zai', 'xai', 'vertexai', 'nvidia', 'openrouter', 'openai', 'ollama', 'llmgateway', 'llamacpp', 'deepseek', 'cerebras', 'azure']; | ||
| const providers: ProviderName[] = ['zai', 'xai', 'vertexai', 'nvidia', 'openrouter', 'openpaths', 'openai', 'ollama', 'llmgateway', 'llamacpp', 'deepseek', 'cerebras', 'azure']; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Adding Useful? React with 👍 / 👎. |
||
| if (isAwsBedrockProviderEnabled(config)) { | ||
| providers.splice(providers.indexOf('azure'), 0, 'bedrock'); | ||
| } | ||
|
|
@@ -189,7 +196,7 @@ export class ProviderFactory { | |
| return false; | ||
| } | ||
|
|
||
| const allProviders: ProviderName[] = ['openrouter', 'ollama', 'openai', 'llamacpp', 'mlx', 'llmgateway', 'azure', 'zai', 'vertexai', 'xai', 'cerebras', 'nvidia', 'deepseek', 'bedrock']; | ||
| const allProviders: ProviderName[] = ['openrouter', 'openpaths', 'ollama', 'openai', 'llamacpp', 'mlx', 'llmgateway', 'azure', 'zai', 'vertexai', 'xai', 'cerebras', 'nvidia', 'deepseek', 'bedrock']; | ||
| return allProviders.includes(name as ProviderName); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a new user selects OpenPaths in the setup wizard,
requiresApiKey()still excludesopenpaths, so onboarding skipspromptApiKey()/validation andcomplete()writes only a model/baseUrl config.getProviderConfig()now requires an API key foropenpaths, so the generated config is incomplete and OpenPaths requests run without authorization until the user hand-edits the config.Useful? React with 👍 / 👎.