Skip to content

Add OpenAI-compatible profiles - #626

Merged
SeoFood merged 2 commits into
mainfrom
seofood/issue-625-openai-compatible-profiles
May 26, 2026
Merged

Add OpenAI-compatible profiles#626
SeoFood merged 2 commits into
mainfrom
seofood/issue-625-openai-compatible-profiles

Conversation

@SeoFood

@SeoFood SeoFood commented May 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds backward-compatible plugin role expansion protocols for additional transcription engines and LLM providers.
  • Reworks OpenAI Compatible into named profiles with legacy default-profile migration, per-profile API keys, base URLs, models, fetched model caches, and temperature settings.
  • Exposes profile IDs through transcription, prompt processing, workflow/API discovery, and bumps the host app to 1.5.0 plus OpenAI Compatible to 1.1.0 with minHostVersion: 1.5.0.

Issue Context

Issue #625 asks for multiple OpenAI-compatible profiles so endpoints such as Alter and Inception can be configured independently and selected separately for transcription and LLM prompt processing, without duplicate plugin bundles and without breaking existing single-profile setups.

Closes #625

Daily Build

The scheduled release workflow derives daily tags from MARKETING_VERSION. This branch sets the host app version to 1.5.0, so after it lands on main, the next scheduled daily build will use a v1.5.0-daily.<date> tag.

Test Plan

  • swift test --package-path TypeWhisperPluginSDK --filter OpenAICompatiblePluginTests
  • xcodebuild test -project TypeWhisper.xcodeproj -scheme TypeWhisper -destination 'platform=macOS,arch=arm64' -parallel-testing-enabled NO -only-testing:TypeWhisperTests/APIRouterAndHandlersTests -only-testing:TypeWhisperTests/PluginManifestValidationTests -only-testing:TypeWhisperTests/WorkflowServiceTests CODE_SIGN_IDENTITY='-' CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
  • git diff --check
  • swift test --package-path TypeWhisperPluginSDK
  • xcodebuild test -project TypeWhisper.xcodeproj -scheme TypeWhisper -destination 'platform=macOS,arch=arm64' -parallel-testing-enabled NO CODE_SIGN_IDENTITY='-' CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
  • /Users/marco/Projects/typewhisper-dev-tools/build-typewhisper-mac-dev.sh --run /Users/marco/Projects/typewhisper-mac
  • /Users/marco/Projects/typewhisper-dev-tools/build-typewhisper-plugin-dev.sh OpenAICompatiblePlugin /Users/marco/Projects/typewhisper-mac

Summary by CodeRabbit

  • New Features

    • OpenAI-compatible plugin: multi-profile support with per-profile server, credentials, model selection, temperature, and profile management UI.
    • Plugin framework: plugins can expose multiple selectable LLM and transcription providers.
  • Bug Fixes / Improvements

    • More robust provider lookup and alias handling (case/whitespace-insensitive) and improved fallback when disabling plugins.
  • Chores

    • App version bumped to 1.5.0; plugin version requirements updated.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 12661f8e-96f0-424b-86af-d535cf149253

📥 Commits

Reviewing files that changed from the base of the PR and between e8d9759 and 8ed972f.

📒 Files selected for processing (3)
  • TypeWhisper/Services/PluginManager.swift
  • TypeWhisperPluginSDK/Plugins/OpenAICompatiblePlugin/Tests/OpenAICompatiblePluginTests.swift
  • TypeWhisperTests/APIRouterAndHandlersTests.swift

📝 Walkthrough

Walkthrough

This PR implements multi-profile support for OpenAI-compatible integrations, enabling users to configure and manage multiple independent providers with separate credentials, models, and settings. SDK protocols add stable provider identity and multi-provider hooks, PluginManager/PromptProcessingService discover expanded roles, OpenAI plugin refactors to profiles with UI/persistence, and tests exercise the flow.

Changes

Multi-Profile OpenAI Integration Support

Layer / File(s) Summary
SDK Plugin Identity & Additional Providers Protocols
TypeWhisperPluginSDK/Sources/.../TypeWhisperPlugin.swift
New LLMProviderIdentityProviding adds stable providerId, providerDisplayName, and providerLegacyAliases. LLMProviderPlugin gains derived llmProviderId/llmProviderDisplayName/llmProviderLegacyAliases. AdditionalLLMProvidersProviding and AdditionalTranscriptionEnginesProviding let plugins publish multiple selectable providers/engines.
Plugin Manager & PromptProcessingService Updates
TypeWhisper/Services/PluginManager.swift, TypeWhisper/Services/PromptProcessingService.swift
PluginManager.llmProviders and transcriptionEngines aggregate direct + additional roles. loadedTranscriptionPlugin(for:) matches additional engines by providerId. llmProvider(for:) trims input, rejects empty, and matches case-insensitively across id/display/name/aliases. PromptProcessingService uses stable llmProviderId and llmProviderDisplayName.
OpenAI Profile Model
TypeWhisperPluginSDK/Plugins/OpenAICompatiblePlugin/OpenAICompatiblePlugin.swift (lines 5–90)
Adds OpenAICompatibleProfile (codable, identifiable) with per-profile baseURL, selected transcription/LLM model IDs, temperature mode/value, fetched models, and defaults. FetchedModel gets convenience init.
OpenAI Plugin Initialization, Activation & Persistence
TypeWhisperPluginSDK/Plugins/OpenAICompatiblePlugin/OpenAICompatiblePlugin.swift (lines 71–139)
Plugin maintains profiles array, loads/persists profiles on activation, and exposes non-default profiles as additional LLM/transcription roles via profile-role proxies.
OpenAI Plugin Protocol Implementation
TypeWhisperPluginSDK/Plugins/OpenAICompatiblePlugin/OpenAICompatiblePlugin.swift (lines 162–226)
Transcription and LLM protocol methods delegate to profile-aware overloads that resolve per-profile credentials, base URL, selected models, and temperature at runtime.
OpenAI Plugin Public Profile Management API
TypeWhisperPluginSDK/Plugins/OpenAICompatiblePlugin/OpenAICompatiblePlugin.swift (lines 235–490)
Public APIs for listing snapshots, add/rename/delete profiles, per-profile base URL/API key setters/removers, fetched models, model/LLM selection, and temperature configuration; connection validation adapted per-profile.
OpenAI Plugin Internals & Persistence
TypeWhisperPluginSDK/Plugins/OpenAICompatiblePlugin/OpenAICompatiblePlugin.swift (lines 503–782)
Canonical profile lookup with legacy mapping, per-profile secret key retrieval, temperature directive construction, HTTP helper creation per profile, profile persistence, legacy defaults sync, and OpenAICompatibleProfileRole proxy.
OpenAI Settings UI
TypeWhisperPluginSDK/Plugins/OpenAICompatiblePlugin/OpenAICompatiblePlugin.swift (lines 794–1261)
OpenAICompatibleSettingsView rebuilt with a profile sidebar and detail pane; per-profile server/API key fields, model pickers (dropdowns when fetched models exist; manual entry otherwise), temperature controls, and Test/Refresh operate on selected profile.
Plugin Disable & Fallback Selection
TypeWhisper/Services/PluginManager.swift
When disabling a plugin, compute all transcription provider IDs (direct + additional) exposed by that plugin and reselect the first configured, non-disabled transcription engine if the current selection is disabled.
Plugin Manifest & Project Metadata
TypeWhisperPluginSDK/Plugins/OpenAICompatiblePlugin/manifest.json, TypeWhisper.xcodeproj/project.pbxproj
Bumped OpenAICompatiblePlugin version 1.0.10→1.1.0 and minHostVersion 0.9.0→1.5.0. Bumped project MARKETING_VERSION 1.4.0→1.5.0 for app, CLI, and widget.
Integration & Unit Tests
TypeWhisperPluginSDK/Plugins/OpenAICompatiblePlugin/Tests/*, TypeWhisperTests/APIRouterAndHandlersTests.swift
New tests for legacy migration into default profile, additional profile creation/deletion, profile-scoped transcription/LLM with separate hosts/keys/models/temperature, and provider discovery/alias resolution. Adds ExpandedRolePlugin, NamedTranscriptionPlugin, and updates MockLLMProviderPlugin identity.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Profiles sprout like clover bright,
Each with keys and servers right,
Providers named and IDs that stay,
Plugins sing in a multi-profile way,
Tests hop in — everything's alight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the main change: adding profile support to OpenAI-compatible integration, which is the primary objective.
Description check ✅ Passed Description includes all required sections: summary of changes, context from issue #625, and comprehensive test plan with multiple testing approaches.
Linked Issues check ✅ Passed All coding requirements from issue #625 are met: multiple named profiles (#625), independent configuration selection (#625), backward compatibility (#625), and no duplicate bundles needed (#625).
Out of Scope Changes check ✅ Passed All changes directly support issue #625 objectives. Version bumps (1.4.0→1.5.0, OpenAI plugin to 1.1.0) are appropriate scope for profile feature launch.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch seofood/issue-625-openai-compatible-profiles

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
TypeWhisperPluginSDK/Plugins/OpenAICompatiblePlugin/Tests/OpenAICompatiblePluginTests.swift (1)

231-268: ⚡ Quick win

Consider testing both default and additional profile LLM for symmetry.

While this test correctly validates profile-scoped LLM behavior for an additional profile, testProfileSpecificTranscriptionUsesSeparateCredentialsAndURLs tests both the default profile and an additional profile, providing more comprehensive coverage. Consider adding a default profile setup with its own model/temperature and verifying both profiles work independently in a single test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@TypeWhisperPluginSDK/Plugins/OpenAICompatiblePlugin/Tests/OpenAICompatiblePluginTests.swift`
around lines 231 - 268, The test
testProfileSpecificLLMUsesSeparateCredentialModelAndTemperature should also set
up and assert behavior for the default profile so both default and additional
profiles are validated; add configuration calls on the plugin for the default
profile (e.g., plugin.setApiKey, plugin.selectLLMModel,
plugin.setLLMTemperatureMode/.setLLMTemperatureValue) before creating the
"Inception" profile, then call the provider(s) for the default profile (use
plugin.additionalLLMProviders and the plugin's primary LLM provider or lookup by
profile id) and assert the default request used the default baseURL,
Authorization header, model and temperature, while keeping the existing
assertions for inception (refer to PluginTestHostServices,
OpenAICompatiblePlugin, plugin.addProfile, additionalLLMProviders, and
provider.process to locate relevant code).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@TypeWhisper/Services/PluginManager.swift`:
- Around line 346-356: The current lookup in PluginManager.swift uses a single
first { ... } that mixes llmProviderId, display name, providerName and aliases
so a display-name/alias from an earlier provider can shadow a later provider
whose llmProviderId exactly matches providerName; change the logic to first try
to find an exact id match using
provider.llmProviderId.caseInsensitiveCompare(lookup) == .orderedSame, and only
if that returns nil fall back to a second pass that checks display name,
providerName and legacy aliases (case-insensitive) against lookup; keep the
existing trimming/guard and return type, and reference llmProviders,
provider.llmProviderId, provider.llmProviderDisplayName, provider.providerName
and provider.llmProviderLegacyAliases when making the two-pass search.
- Around line 287-296: The disable-path currently only checks if plugin.instance
as? TranscriptionEnginePlugin equals the selected provider, but plugins can also
expose additionalTranscriptionEngines via
AdditionalTranscriptionEnginesProviding; when disabling a plugin you must also
check if the selected transcription engine is one of plugin.instance as?
AdditionalTranscriptionEnginesProviding -> additionalTranscriptionEngines and,
if so, pick and set a safe fallback (e.g., another available
TranscriptionEnginePlugin) and update the selected/active transcription engine
state; modify the disable logic to compare against both the direct
TranscriptionEnginePlugin and the additionalTranscriptionEngines collection
before leaving the active provider unchanged.

---

Nitpick comments:
In
`@TypeWhisperPluginSDK/Plugins/OpenAICompatiblePlugin/Tests/OpenAICompatiblePluginTests.swift`:
- Around line 231-268: The test
testProfileSpecificLLMUsesSeparateCredentialModelAndTemperature should also set
up and assert behavior for the default profile so both default and additional
profiles are validated; add configuration calls on the plugin for the default
profile (e.g., plugin.setApiKey, plugin.selectLLMModel,
plugin.setLLMTemperatureMode/.setLLMTemperatureValue) before creating the
"Inception" profile, then call the provider(s) for the default profile (use
plugin.additionalLLMProviders and the plugin's primary LLM provider or lookup by
profile id) and assert the default request used the default baseURL,
Authorization header, model and temperature, while keeping the existing
assertions for inception (refer to PluginTestHostServices,
OpenAICompatiblePlugin, plugin.addProfile, additionalLLMProviders, and
provider.process to locate relevant code).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2753f837-1833-47e4-8e49-cd9a21bb2287

📥 Commits

Reviewing files that changed from the base of the PR and between 7982d41 and e8d9759.

📒 Files selected for processing (8)
  • TypeWhisper.xcodeproj/project.pbxproj
  • TypeWhisper/Services/PluginManager.swift
  • TypeWhisper/Services/PromptProcessingService.swift
  • TypeWhisperPluginSDK/Plugins/OpenAICompatiblePlugin/OpenAICompatiblePlugin.swift
  • TypeWhisperPluginSDK/Plugins/OpenAICompatiblePlugin/Tests/OpenAICompatiblePluginTests.swift
  • TypeWhisperPluginSDK/Plugins/OpenAICompatiblePlugin/manifest.json
  • TypeWhisperPluginSDK/Sources/TypeWhisperPluginSDK/TypeWhisperPlugin.swift
  • TypeWhisperTests/APIRouterAndHandlersTests.swift

Comment thread TypeWhisper/Services/PluginManager.swift
Comment thread TypeWhisper/Services/PluginManager.swift
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multiple OpenAI compatible integrations

1 participant