Releases: mikealicea/obsidian-scribe
Release list
Scribe 2.4.3 feat: Recordings now continue when the controls modal is closed
Scribe v2.4.3 Recordings now continue when the controls modal is closed
Previously, clicking outside the "Open Controls" modal (or pressing ESC) silently cancelled an in-progress recording — nothing was saved. This release changes that behavior, as requested in #113.
What's new:
- Recording survives modal close — dismissing the controls modal no longer stops your recording. It keeps running in the background.
- Tappable recording notice — when you close the modal mid-recording, the familiar persistent notice (
🔴 Scribe: Recording 01:23 — Tap to save) appears with a live timer. Tap it anytime to stop and save. - Reopen and pick up where you left off — opening the controls modal again (via the ribbon or command palette) re-attaches to the live recording: the timer, pause/resume state, and your session options are all restored so you can pause, resume, or complete from there.
- Session options are remembered — options you set in the modal (only transcribe, languages, note template, etc.) now apply however you finish the recording: tapping the notice, the ribbon's "Stop Recording", or the Start/Stop command.
- "Open Controls" in the ribbon menu while recording — the ribbon menu now includes Open Controls alongside Pause/Cancel/Stop during an active recording.
Behavior notes:
- Cancelling a recording is now always an explicit action — the modal's Reset button or the ribbon's Cancel Recording. Nothing is discarded just by closing a window.
- Fixed: opening the modal during a recording started from the command palette previously left the background notice running alongside the modal. The notice now hides while the modal is open.
Thanks to the user who reported #113 🙏
What's Changed
- 113 open controls modal window stoping in the background by @Mikodin in #114
Full Changelog: 2.4.1...2.4.2
What's Changed
- 113 open controls modal window stoping in the background by @Mikodin in #114
Full Changelog: 2.4.1...2.4.3
Scribe 2.4.2 feat: Recordings now continue when the controls modal is closed
Scribe v2.4.2
🎙️ Recordings now continue when the controls modal is closed
Previously, clicking outside the "Open Controls" modal (or pressing ESC) silently cancelled an in-progress recording — nothing was saved. This release changes that behavior, as requested in #113.
What's new:
- Recording survives modal close — dismissing the controls modal no longer stops your recording. It keeps running in the background.
- Tappable recording notice — when you close the modal mid-recording, the familiar persistent notice (
🔴 Scribe: Recording 01:23 — Tap to save) appears with a live timer. Tap it anytime to stop and save. - Reopen and pick up where you left off — opening the controls modal again (via the ribbon or command palette) re-attaches to the live recording: the timer, pause/resume state, and your session options are all restored so you can pause, resume, or complete from there.
- Session options are remembered — options you set in the modal (only transcribe, languages, note template, etc.) now apply however you finish the recording: tapping the notice, the ribbon's "Stop Recording", or the Start/Stop command.
- "Open Controls" in the ribbon menu while recording — the ribbon menu now includes Open Controls alongside Pause/Cancel/Stop during an active recording.
Behavior notes:
- Cancelling a recording is now always an explicit action — the modal's Reset button or the ribbon's Cancel Recording. Nothing is discarded just by closing a window.
- Fixed: opening the modal during a recording started from the command palette previously left the background notice running alongside the modal. The notice now hides while the modal is open.
Thanks to the user who reported #113 🙏
What's Changed
- 113 open controls modal window stoping in the background by @Mikodin in #114
Full Changelog: 2.4.1...2.4.2
Scribe v2.4.1 - chore: A health and stability release focused on clearing all warnings
Scribe v2.4.1
A health and stability release focused on clearing all Obsidian plugin review warnings — no new features, but several real bug fixes surfaced along the way.
⚠️ Requirement change
- Minimum Obsidian version is now 1.8.7 (released Feb 2025). Scribe already relied on newer APIs like
Vault.processand frontmatter processing; the manifest now declares this honestly instead of claiming compatibility with 0.15.0.
🐛 Bug fixes
- Deleting a recording now respects your "Deleted files" preference — audio cleanup uses Obsidian's trash instead of permanently deleting the file.
- Fixed a broken filename-collision retry when creating notes: the fallback path compared an error object to a string, silently discarded its own retry, and re-threw anyway. Collisions are handled by the random-suffix path, and the dead code is gone.
- Append-to-active-file with no active file no longer relies on an unsafe type cast — the fallback (create a new note and open it) is now properly null-checked.
- Recording stop errors now always reject with a real
Error, so failure messages surface correctly instead of as opaque values.
🔒 Security
- Resolved all
npm auditadvisories (2 high, 1 moderate):langsmith0.5.4 → 0.8.2 (prototype pollution, redaction bypass)ws8.19.0 → 8.21.1 (memory disclosure, DoS)uuidremoved from the dependency tree entirely
- Replaced the banned
dotenvpackage with Node's built-inprocess.loadEnvFile()(build tooling only).
🧹 Code health
- Cleared the full Obsidian review-bot
- All floating promises are now explicitly awaited or fire-and-forget (
void) -data:URLs are decoded locally inativefetch - Modal
onOpen/onClosenow match Obsidian's declared signatures - Removed unsafeas TFile/ `as HTMd unnecessary assertions - Enum comparisons are now type-safe (
OBSIDIAN_PATHSconverted to a const object; typed language-option helpers) - Callback props declare
this: voidto prevent accidentalthisrebinding
- All floating promises are now explicitly awaited or fire-and-forget (
What's Changed
- Scribe v2.4.1 - chore: A health and stability release focused on clearing all warnings by @Mikodin in #112
Full Changelog: 2.4.0...2.4.1
Scribe 2.4.0 - Fixes Obsidian plugin review blocker: dynamic `<script>` element creations
v2.4.0 Fixed Obsidian plugin review blocker: dynamic <script> element creations
The automated Obsidian plugin review flagged 4 dynamic <script> element creations in the bundled main.js. All 4 came from third-party dependencies — none from Scribe's own code — and have been fully removed from the bundle.
Changes
- Downgraded
react/react-domfrom v19 to v18.3.1 — React 19 introduced "hoistable scripts" support (ReactDOM.preinit), which shipscreateElement("script")code paths in the production bundle even though Scribe never uses them. Scribe only uses standard hooks andcreateRoot, all fully supported in React 18.3, so this is a drop-in downgrade with no functional changes. - Removed the
standardized-audio-contextdependency — its AudioWorklet polyfill loads code via a Blob-URL<script>tag. Scribe only used it forAudioContext/decodeAudioData/createBufferduring audio chunking, all of which are natively available in Obsidian's desktop and mobile environments. The audio chunking pipeline (audioDataToChunkedFiles) now uses the native Web Audio API directly, with awebkitAudioContextfallback for older iOS WebViews.
Verification
- Production bundle now contains 0 dynamic script element creations (previously 4)
- Also scanned the bundle for
eval(,new Function(, anddocument.write— all clean - Typecheck, lint, and production build all pass
No user-facing behavior changes — recording, transcription, and summarization work exactly as before.
A note on where verification stands: I confirmed the bundle is clean via static checks and the full typecheck/build, but the headless-Chrome runtime test of the audio chunking path was blocked by the sandbox (Chrome can't launch from this session). Since decodeAudioData/createBuffer have identical signatures between the removed library and the native API, risk is low — but I'd recommend one manual smoke test: record a short clip with OpenAI transcription selected before publishing the release, since that's the path that exercises the refactored chunking code.
What's Changed
- Fix: Downgrades react and removes standardized-audio-context by @Mikodin in #111
Full Changelog: 2.3.9...2.4.0
Scribe v2.3.9 — Flexible Transcription Placement & Formatting
Scribe v2.3.9 — Flexible Transcription Placement & Formatting
This completes the feature requests
#67
#100
You can now control where the audio embed and transcript land in your generated notes, how their headers look, and whether the transcript is collapsed by default. Previously the # Audio block and transcript were always pinned to the top with a fixed H1 header and always sat above your summary sections — now it's all up to you.
✨ What's new
Audio & Transcript are now template sections
Every note template has two new built-in sections — 📌 Audio and 📌 Transcript — that sit right alongside your LLM sections (Summary, Insights, etc.) in the template editor. Their position in the template is their position in the finished note.
Reorder anything with ▲ / ▼
Each section now has move-up/move-down buttons. Want the transcript below your summary? Move it down. Want the audio embed at the very bottom? Drag it there. Placement in the note follows the order in the template.
Custom section headers
Section headers are now the full markdown line, so you decide the heading level. Issue #100's request is covered: change # Audio to ## Audio, or leave a header blank for no heading at all. This applies to your LLM sections too — set them to ##, ###, or nothing.
Collapse the transcript by default
The Transcript section has a new "Collapse transcript by default" toggle that renders it inside an Obsidian foldable callout, so long transcripts stay tucked away until you click to expand them.
🛠 Improvements & fixes
- Notes now appear instantly and fill in as they go. The full note structure — audio embed, headers, and "in progress" placeholders — is written up front, then each section is replaced in place as transcription and summarization complete, instead of appending at the end.
- Cleaner failures. If transcription or summarization fails, Scribe now leaves a clear marker and removes the leftover placeholders rather than stranding an "in progress" line in your note.
- Fixed: Cloning a template no longer shares section data with the original (edits to one used to leak into the other).
- Fixed: Transcripts or summaries conta or $&) can no longer corrupt notecontent.
🔄 Upgrading
Your existing templates migrate automatically on first launch — the Audio and Transcript sections are inserted at the top in their current poeaders without a level are normalized toH2. Existing notes are unaffected and new notes come out identical to before unless you choose to rearrange things.
Full Changelog: 2.3.8...2.3.9
Scribe 2.3.8 - Bug Fix: Don't make LLM provider request if there is no transcript
Fix: Skip LLM processing when transcription is disabled (#104)
Fixes #104
Problem
The "Transcribe recordings" and "Process transcriptions with LLM" settings were fully independent. With transcription off and processing on, handleTranscription() returned an empty string and the pipeline sent it straight to the LLM — resulting in a pointless (and paid) LLM call that generated garbage note sections.
Changes
- Settings UI (
src/settings/GeneralSettingsTab.tsx): "Process transcriptions with LLM" is now disabled and displayed as off while "Transcribe recordings" is off. The stored preference is left untouched, so re-enabling transcription restores the previous processing state. Description updated to note the dependency. - Toggle component (
src/settings/components/SettingsControl.tsx):SettingsTogglenow supports adisabledprop (click and Enter-key handlers are guarded). - Pipeline guard (
src/index.ts,handleScribeFile): if the transcript is empty, LLM summarization is skipped with aScribe: ⚠️ Skipping LLM processing — transcript is emptynotice. This covers stale persisted settings, the modal inheriting the disabled-transcription flag, and any provider legitimately returning an empty transcript.
Testing
npm run format:write,npm run lint:write,npm run build:prodall pass- Manual: with "Transcribe recordings" off, the processing toggle is off/unclickable; completing a recording shows the skip notice instead of calling the LLM. Re-enabling transcription restores the previous processing preference.
What's Changed
- Bug Fix: Don't make LLM provider request if there is no transcript by @Mikodin in #110
Full Changelog: 2.3.7...2.3.8
Scribe 2.3.7 feat: add AssemblyAI LLM support with model selection and configuration
Scribe 2.3.7 feat: add AssemblyAI LLM support with model selection and configuration
Add AssemblyAI as an LLM summarization provider
Summary
Adds AssemblyAI's LLM Gateway as a summarization provider, so a user can run both transcription and LLM summarization through AssemblyAI — keeping data within a single vendor's privacy envelope (the motivation behind this ZDR / no-training branch) instead of sending transcripts to a second provider.
Because the gateway is OpenAI-compatible, it routes through the existing createOpenAiCompatibleLlmAdapter (LangChain ChatOpenAI + obsidianFetch + a baseURL override) — the same mechanism already used for Gemini and OpenRouter. No new transcription/LLM call logic was required; this is almost entirely wiring a new provider through the existing data-driven plumbing.
What changed
New provider wiring
- src/util/consts.ts — added assemblyAi to the PROCESS_PLATFORM enum.
- src/aiProviders/llm/openAiCompatibleLlm.ts — added the gateway base URL, default model (claude-sonnet-4-6), and a seed list of common model IDs.
- src/aiProviders/llm/llmAdapter.ts — resolveLlmConfig case mapping the provider to assemblyAiApiKey + assemblyAiLlmModel + the gateway base URL. createLlmAdapter needed no change (non-Anthropic already routes to the OpenAI-compat adapter).
- src/aiProviders/providerMetadata.ts — LLM_PROVIDERS entry (drives the settings dropdown, privacy card, and missing-key checks).
- src/settings/settings.tsx — new assemblyAiLlmModel setting, defaulted to claude-sonnet-4-6.
UI (free-text model combobox, mirroring OpenRouter)
- src/settings/components/ProviderSettingsSections.tsx — AssemblyAI section reusing the existing API-key input + a combobox seeded with known model IDs (any gateway model ID allowed).
- src/modal/components/options/ModalAiModelOptions.tsx — matching combobox branch in the per-run modal picker.
Bug fixes (gateway-specific)
Summarization initially failed only with AssemblyAI. Two gateway behaviors differ from OpenAI/Gemini/OpenRouter, both fixed in openAiCompatibleLlm.ts:
- Output truncation — the gateway defaults max_tokens to 1000 when omitted (unlike OpenAI/Gemini, which use the model's high default). LangChain doesn't send max_tokens, so full structured notes were truncated mid-JSON and failed to parse. Fixed by sending an explicit maxTokens: 8192 for the gateway.
- Dropped transcript — the transcript lives in the first system message, and we always send ≥2 system messages (the language instruction defaults to en). When the gateway collapses OpenAI's multiple system messages into Anthropic's single system field for Claude models, the transcript-bearing message could be lost. Fixed by merging the main prompt + extra instructions into a single system message (behavior-equivalent for the other OpenAI-compat providers).
ZDR note
No special request headers are needed — Zero Data Retention on the LLM Gateway is an account-level arrangement (executed BAA + training opt-out + optional TTL) and applies
only when routing to Anthropic or Google models, not GPT/Qwen/Kimi. The de ZDR-eligible; users who select a gpt-* model in the combobox fall outsidethe ZDR envelope.
Testing
- npm run format:write, npm run lint:write, npm run build:prod all pass cleanly (incl. the mobile-safety check).
- Manual verification still needed in Obsidian: with an AssemblyAI key ander selected, confirm the note gets its summary sections + LLM-suggestedtitle. If it fails, the gateway's error body surfaces in the console via [obsidianFetch] error body:.
(Note: the branch also switches the AssemblyAI transcription default speech model to universal-3-5-pro in assemblyAiTranscriber.ts.)
What's Changed
- Scribe 2.3.7 feat: add AssemblyAI LLM support with model selection and configuration by @Mikodin in #109
Full Changelog: 2.3.6...2.3.7
Scribe 2.3.6 Adds a ton of providers for transcription + summarizing
Scribe 2.3.6 Adds a ton of providers for transcription + summarizing
Multi-provider AI refactor: transcription + summarization
Tested every provider on mobile and desktop
Summary
Replaces the single-LLM (OpenAI only) / two-transcriber (OpenAI + AssemblyAI) setup with a provider-adapter architecture supporting 6 transcription providers and 5 LLM providers, plus inline privacy/data-retention info for each and a searchable model picker for OpenRouter.
- LLM (summarization + mermaid-fix): OpenAI, Anthropic, Google (Gemini), OpenRouter, custom OpenAI-compatible endpoint
- Transcription: OpenAI Whisper, AssemblyAI, ElevenLabs Scribe, Deepgram Nova-3, Mistral Voxtral, Google (Gemini), custom OpenAI-compatible endpoint
Why this shape
PR #101 attempted Google support via @langchain/google-genai and was reverted (#102): that package transitively pulls in node:async_hooks, which crashes Obsidian mobile (WebView, no Node runtime). This PR avoids that class of bug entirely:
- Anthropic uses the native
@anthropic-ai/sdk(browser-safe,dangerouslyAllowBrowser: true) — the only new LangChain-adjacent dependency added. - Google, OpenRouter, and custom endpoints all route through the existing LangChain
ChatOpenAIclient viabaseURL, since Gemini and OpenRouter both expose OpenAI-compatible chat completions endpoints. No Google SDK, no new provider packages. - ElevenLabs, Deepgram, Mistral, and Gemini transcription are raw
fetchcalls through the existingobsidianFetchCORS wrapper — no SDKs at all. - A new
scripts/checkMobileSafe.mjsbuild step grepsbuild/main.jsforasync_hooks/AsyncLocalStorageand fails the production build if either appears — thithat would have caught the #101 bug before merge.
Architecture
New src/aiProviders/ module:
src/aiProviders/
providerMetadata.ts # registry: diseld, keyConsoleUrl,
# models?, supportsDiarization?, privacy info
prompts.ts # shared promptd from the old
# openAiUtils.ts, byte-identical prompt text)
llm/
llmAdapter.ts # LlmAdapter interface + resolveLlmConfig() + createLlmAdapter()
openAiCompatibleLlm.ts # LangChain Ch google / openRouter / customOpenAi
anthropicLlm.ts # native @anthropic-ai/sdk adapter
openRouterModels.ts # fetches + cacatalog
transcription/
transcriptionAdapter.ts # dispatches oPlatform
openAiTranscriber.ts # moved from src/util/openAiUtils.ts
assemblyAiTranscriber.ts # moved from s unchanged
elevenLabsTranscriber.ts
deepgramTranscriber.ts
mistralTranscriber.ts
geminiTranscriber.ts
diarizationFormat.ts # shared "**Speaker N**: text" formatter
Deleted: src/util/openAiUtils.ts, src/util/split),src/settings/components/AiModelSettings.tsx` (replaced by the registry-driven settings UI).
src/util/obsidianFetch.ts wraps Obsidian's requestUrl() as a CORS-bypassing fetch implementation — used by every
HTTP-based provider client (OpenAI SDK, Anthroetch calls). Since requestUrl() buffers the fullresponse, no provider call streams — all LLM calls use non-streaming structured output.
Provider details
LLM
| Provider | Transport | Models | Notes |
|---|---|---|---|
| OpenAI | LangChain ChatOpenAI |
GPT-5.6/5.5/5.1/5/4.1/4o family | unchanged from before |
| Anthropic | @anthropic-ai/sdk, client.mestFormat |
claude-opus-4-8, claude-sonnet-5(default), claude-sonnet-4-6, claude-haiku-4-5 | never sends temperature (current Claude models reject non-default |
values); surfaces refusal/max_tokens stop |
|||
| Google (Gemini) | LangChain ChatOpenAI via Gemini's OpenAI-compat endpoint |
||
| (`generativelanguage.googleapis.com/v1beta/opedefault, GA), gemini-3.1-pro-preview,gemini-3-flash-preview, gemini-3.1-flash-lite, gemini-2.5-pro, gemini-2.5-flash | no Google SDK — sidesteps the async_hooks | ||
| issue entirely | |||
| OpenRouter | LangChain ChatOpenAI via openrouter.ai/api/v1 |
any model id, searchable combobox (see below) | |
| temperature omitted — OpenRouter routes to arbs/models with their own constraints | |||
| Custom OpenAI-compatible | LangChain ChatOpenAI via user-supplied baseURL |
free text | unchanged from before |
Transcription
| Provider | Transport | Diarization | Notes |
|---|---|---|---|
| OpenAI Whisper | OpenAI SDK, chunked WAV upload | No | unchanged from before |
| AssemblyAI | assemblyai SDK |
Yes | unchand through obsidianFetch) |
| ElevenLabs Scribe | raw fetch, multipart |
Yes (word-level → merged into speaker runs) | |
| Deepgram Nova-3 | raw fetch, raw audio byt) |
||
| Mistral Voxtral | raw fetch, multipart |
No | |
| Google (Gemini) | raw fetch to `generateCoo |
Yes (prompt-driven) | no dedicated STT endpoint — audio is chunked to WAV (reusing the existing chunker) to stay under the 20MB inline-request limit, sent with a |
| transcription prompt |
All diarized output converges on the same `**Son regardless of provider.
Settings UI
- AI Providers tab: separate "Transcription" as, each with a provider dropdown, that provider'sAPI key + model fields inline, and a privacy card — retention summary, whether the provider trains on your API data,
and a policy link. Facts are July-2026 snapshopolicies drift. - OpenRouter model field is now a searchable combobox (native
<input list>+<datalist>, no new dependency) — it
fetches OpenRouter's public model catalog (no ers as you type, while still accepting arbitraryfree text. Same picker is used in the modal's per-run model override, which was previously a disabled read-only input for
OpenRouter. - Settings migrate automatically: the old
useCustomOpenAiBaseUrlboolean maps toprocessPlatform/transcriptPlatform = customOpenAi; everyone else is unaffected. "R all API keys (now 7 of them).
Bug fix (behavior change)
handleTranscription previously read this.se, ignoring the modal's per-runscribeOptions.transcriptPlatform` override — so switching providers in the modal had no effect on the actual request. This
is now fixed; modal overrides take effect. Worince it changes observable behavior for anyone who was relying on (or unknowingly affected by) the old ignored-override behavior.
Build safety
- Fixed a broken esbuild config:
builtin-modules@5is ESM-only, so its default import silently resolved toundefined
and Node builtins were being bundled unnoticedle'sbuiltinModules(bare +node:`-prefixedvariants). - Added
scripts/checkMobileSafe.mjs, wired ifails the build ifbuild/main.jscontainsasync_hooksorAsyncLocalStorage.
Verification
npm run format:write— cleannpm run build:prod(tsc + esbuild + mobile- Confirmed all provider endpoints present in
build/main.js(ElevenLabs, Deepgram, Mistral, Gemini generateContent +
OpenAI-compat, OpenRouter, Anthropic) - Confirmed extracted summary prompt text is unchanged (byte-identical modulo trailing whitespace) vs. the original
openAiUtils.ts
Not covered by this PR (needs real API keyse)
- Live provider calls per transcription providses for ElevenLabs/Deepgram/Mistral/Gemini)
- Anthropic and Gemini structured-output round-trips against real audio/transcripts
- OpenRouter combobox behavior against the liv
- Settings migration against a hand-edited
data.jsonwithuseCustomOpenAiBaseUrl: true - Mobile sideload smoke test
- Mistral Voxtral's file-size limit is unverified (chunking fallback noted in code but untested)
What's Changed
- Feat: Add support for all major providers by @Mikodin in #108
Full Changelog: 2.3.4...2.3.6
Scribe 2.3.5
Scribe 2.3.5 Adds a ton of providers for transcription + summarizing
Multi-provider AI refactor: transcription + summarization
Tested every provider on mobile and desktop
Summary
Replaces the single-LLM (OpenAI only) / two-transcriber (OpenAI + AssemblyAI) setup with a provider-adapter architecture supporting 6 transcription providers and 5 LLM providers, plus inline privacy/data-retention info for each and a searchable model picker for OpenRouter.
- LLM (summarization + mermaid-fix): OpenAI, Anthropic, Google (Gemini), OpenRouter, custom OpenAI-compatible endpoint
- Transcription: OpenAI Whisper, AssemblyAI, ElevenLabs Scribe, Deepgram Nova-3, Mistral Voxtral, Google (Gemini), custom OpenAI-compatible endpoint
Why this shape
PR #101 attempted Google support via @langchain/google-genai and was reverted (#102): that package transitively pulls in node:async_hooks, which crashes Obsidian mobile (WebView, no Node runtime). This PR avoids that class of bug entirely:
- Anthropic uses the native
@anthropic-ai/sdk(browser-safe,dangerouslyAllowBrowser: true) — the only new LangChain-adjacent dependency added. - Google, OpenRouter, and custom endpoints all route through the existing LangChain
ChatOpenAIclient viabaseURL, since Gemini and OpenRouter both expose OpenAI-compatible chat completions endpoints. No Google SDK, no new provider packages. - ElevenLabs, Deepgram, Mistral, and Gemini transcription are raw
fetchcalls through the existingobsidianFetchCORS wrapper — no SDKs at all. - A new
scripts/checkMobileSafe.mjsbuild step grepsbuild/main.jsforasync_hooks/AsyncLocalStorageand fails the production build if either appears — thithat would have caught the #101 bug before merge.
Architecture
New src/aiProviders/ module:
src/aiProviders/
providerMetadata.ts # registry: diseld, keyConsoleUrl,
# models?, supportsDiarization?, privacy info
prompts.ts # shared promptd from the old
# openAiUtils.ts, byte-identical prompt text)
llm/
llmAdapter.ts # LlmAdapter interface + resolveLlmConfig() + createLlmAdapter()
openAiCompatibleLlm.ts # LangChain Ch google / openRouter / customOpenAi
anthropicLlm.ts # native @anthropic-ai/sdk adapter
openRouterModels.ts # fetches + cacatalog
transcription/
transcriptionAdapter.ts # dispatches oPlatform
openAiTranscriber.ts # moved from src/util/openAiUtils.ts
assemblyAiTranscriber.ts # moved from s unchanged
elevenLabsTranscriber.ts
deepgramTranscriber.ts
mistralTranscriber.ts
geminiTranscriber.ts
diarizationFormat.ts # shared "**Speaker N**: text" formatter
Deleted: src/util/openAiUtils.ts, src/util/split),src/settings/components/AiModelSettings.tsx` (replaced by the registry-driven settings UI).
src/util/obsidianFetch.ts wraps Obsidian's requestUrl() as a CORS-bypassing fetch implementation — used by every
HTTP-based provider client (OpenAI SDK, Anthroetch calls). Since requestUrl() buffers the fullresponse, no provider call streams — all LLM calls use non-streaming structured output.
Provider details
LLM
| Provider | Transport | Models | Notes |
|---|---|---|---|
| OpenAI | LangChain ChatOpenAI |
GPT-5.6/5.5/5.1/5/4.1/4o family | unchanged from before |
| Anthropic | @anthropic-ai/sdk, client.mestFormat |
claude-opus-4-8, claude-sonnet-5(default), claude-sonnet-4-6, claude-haiku-4-5 | never sends temperature (current Claude models reject non-default |
values); surfaces refusal/max_tokens stop |
|||
| Google (Gemini) | LangChain ChatOpenAI via Gemini's OpenAI-compat endpoint |
||
| (`generativelanguage.googleapis.com/v1beta/opedefault, GA), gemini-3.1-pro-preview,gemini-3-flash-preview, gemini-3.1-flash-lite, gemini-2.5-pro, gemini-2.5-flash | no Google SDK — sidesteps the async_hooks | ||
| issue entirely | |||
| OpenRouter | LangChain ChatOpenAI via openrouter.ai/api/v1 |
any model id, searchable combobox (see below) | |
| temperature omitted — OpenRouter routes to arbs/models with their own constraints | |||
| Custom OpenAI-compatible | LangChain ChatOpenAI via user-supplied baseURL |
free text | unchanged from before |
Transcription
| Provider | Transport | Diarization | Notes |
|---|---|---|---|
| OpenAI Whisper | OpenAI SDK, chunked WAV upload | No | unchanged from before |
| AssemblyAI | assemblyai SDK |
Yes | unchand through obsidianFetch) |
| ElevenLabs Scribe | raw fetch, multipart |
Yes (word-level → merged into speaker runs) | |
| Deepgram Nova-3 | raw fetch, raw audio byt) |
||
| Mistral Voxtral | raw fetch, multipart |
No | |
| Google (Gemini) | raw fetch to `generateCoo |
Yes (prompt-driven) | no dedicated STT endpoint — audio is chunked to WAV (reusing the existing chunker) to stay under the 20MB inline-request limit, sent with a |
| transcription prompt |
All diarized output converges on the same `**Son regardless of provider.
Settings UI
- AI Providers tab: separate "Transcription" as, each with a provider dropdown, that provider'sAPI key + model fields inline, and a privacy card — retention summary, whether the provider trains on your API data,
and a policy link. Facts are July-2026 snapshopolicies drift. - OpenRouter model field is now a searchable combobox (native
<input list>+<datalist>, no new dependency) — it
fetches OpenRouter's public model catalog (no ers as you type, while still accepting arbitraryfree text. Same picker is used in the modal's per-run model override, which was previously a disabled read-only input for
OpenRouter. - Settings migrate automatically: the old
useCustomOpenAiBaseUrlboolean maps toprocessPlatform/transcriptPlatform = customOpenAi; everyone else is unaffected. "R all API keys (now 7 of them).
Bug fix (behavior change)
handleTranscription previously read this.se, ignoring the modal's per-runscribeOptions.transcriptPlatform` override — so switching providers in the modal had no effect on the actual request. This
is now fixed; modal overrides take effect. Worince it changes observable behavior for anyone who was relying on (or unknowingly affected by) the old ignored-override behavior.
Build safety
- Fixed a broken esbuild config:
builtin-modules@5is ESM-only, so its default import silently resolved toundefined
and Node builtins were being bundled unnoticedle'sbuiltinModules(bare +node:`-prefixedvariants). - Added
scripts/checkMobileSafe.mjs, wired ifails the build ifbuild/main.jscontainsasync_hooksorAsyncLocalStorage.
Verification
npm run format:write— cleannpm run build:prod(tsc + esbuild + mobile- Confirmed all provider endpoints present in
build/main.js(ElevenLabs, Deepgram, Mistral, Gemini generateContent +
OpenAI-compat, OpenRouter, Anthropic) - Confirmed extracted summary prompt text is unchanged (byte-identical modulo trailing whitespace) vs. the original
openAiUtils.ts
Not covered by this PR (needs real API keyse)
- Live provider calls per transcription providses for ElevenLabs/Deepgram/Mistral/Gemini)
- Anthropic and Gemini structured-output round-trips against real audio/transcripts
- OpenRouter combobox behavior against the liv
- Settings migration against a hand-edited
data.jsonwithuseCustomOpenAiBaseUrl: true - Mobile sideload smoke test
- Mistral Voxtral's file-size limit is unverified (chunking fallback noted in code but untested)
What's Changed
- Feat: Add support for all major providers by @Mikodin in #108
Full Changelog: 2.3.4...2.3.5
Scribe 2.3.4 - Update OpenAI Model options
Update AI model options to latest OpenAI models
What
- New models in the summary LLM selector — adds the GPT-5.6 family (
gpt-5.6-sol,gpt-5.6-terra,gpt-5.6-luna) plusgpt-5.5,gpt-5.1,gpt-5,gpt-5-mini, andgpt-5-nanoto theLLM_MODELSenum, which drives both the settings dropdown and the modal's model picker. Existing gpt-4.x entries are kept so previously saved selections still resolve. - New default model — fresh installs now default to
gpt-5.6-terra(OpenAI's recommended balanced tier). Existing users keep their saved setting. - Temperature guard — GPT-5-family and o-series models reject any non-default
temperatureon the Chat Completions API (400 error). Summarization and mermaid-fix calls now only pass their hardcoded temperatures (0.5/0.3) to models that support it. - Docs touch-up — custom chat model placeholder/example text updated from
gpt-4otogpt-5.6-terra.
Why
The selector topped out at gpt-4.1, several generations behind current OpenAI offerings. Without the temperature guard, simply adding the new models would have broken summarization with a 400 on every GPT-5-family request.
Testing
npm run format:writeandnpm run build:prod(typecheck + build) pass cleanly.- No settings migration needed: saved
llmModelvalues remain valid enum members.
What's Changed
- Feat update model options for OpenAI model selection by @Mikodin in #107
Full Changelog: 2.3.3...2.3.4