feat: add MiniMax M3 as a cloud AI provider - #324
Open
M3NT1 wants to merge 9 commits into
Open
Conversation
The toast that warns the user about missing screen-recording access was gated on 'getSavedPreference() == true || appState.isRecording', but the recorder forces isRecording off the moment it detects the permission is missing, and getSavedPreference() is nil for users who have never explicitly toggled it. Net effect: the notice never surfaced for the exact case it was meant to alert on, and the user had no UI signal that recording was broken (the Resume button appeared to do nothing). Switch the guard to the 'didOnboard' UserDefaults flag so the notice appears whenever the user has completed onboarding and the permission is missing. Also re-evaluate the notice on every tab change to .timeline so a user who lands on the timeline after dismissing elsewhere still sees it (the session-dismiss flag prevents spam).
Cards in the timeline now carry a small 'Provider · Model' badge so the user can see at a glance which model produced them. Older cards saved before this change render without the badge (no placeholder), because the new fields are optional in the JSON metadata column — no DB migration required. Three places show the badge: - Canvas timeline card (CanvasActivityCard) — a small text line below the time range - Right-side detail card (MainView/ActivityCard) — a chip next to the category badge with a sparkles icon - Review-mode swipe card (TimelineReviewCard) — a chip next to the category pill A new 'TimelineActivity.providerBadge' computed property centralises the provider-id → human-label mapping (e.g. 'gemini' → 'Gemini', 'chatgpt_claude' → 'Claude' or 'ChatGPT' based on the model name), and looks up the model alias through ClaudeModel / CodexModel enums so 'sonnet' displays as 'Claude Sonnet' rather than the raw alias. Storage: TimelineCard, TimelineCardShell, TimelineActivity, and the TimelineMetadata JSON envelope gained optional 'providerId' and 'modelId' fields. LLMService stamps these onto every card it writes, using 'activeContext.id.providerLabel' for the provider and a new 'providerModelId(for:)' helper for the model.
The Claude CLI's --model flag accepts aliases (sonnet, opus, fable) or full names (claude-fable-5), but rejects the bare 'claude-sonnet' with 'It may not exist or you may not have access to it' → exit 1. Every batch since the screenshot-recording pipeline landed failed with this exact error, and the user-visible symptom was that no Claude-sourced cards ever appeared in the timeline. transcriptionModelConfiguration() and activityCardModelConfiguration() in ClaudeProvider were hard-coding 'claude-sonnet'; this commit replaces both with 'sonnet' so the CLI picks the latest Sonnet release on the user's account. Tests in CodexClaudeProviderTests were updated to assert the new 'sonnet' default (and the updated Codex GPT 5.6-luna default, which is handled in a separate commit).
The Settings → Providers tab now offers a model picker for the
Chat CLI providers (Claude and Codex), mirroring the existing
Gemini picker. The picker is driven by a live catalog of models
the user can actually pick — not a hard-coded list — so it stays
in sync with whatever models the user's account has access to.
Discovery: a new ChatCLIModelCatalog runs one tiny 'claude -p
--model <alias> OK' probe per known alias in parallel and pulls
the resolved full model name (e.g. 'claude-sonnet-5') out of the
JSONL response's 'modelUsage' key. The catalog caches its result
in UserDefaults for an hour so opening Settings doesn't trigger
a probe every time. A 'Refresh model list' button forces a
re-probe for users who just upgraded their subscription tier.
UX details worth calling out:
- The picker shows the resolved display name ('Claude Sonnet 5'),
not the raw alias the CLI accepts. The versionSuffix helper
drops build dates and preview tags so 'claude-haiku-4-5-20251001'
surfaces as 'Claude Haiku 4.5'.
- Provider IDs are mapped to friendly labels in one place
(ChatCLIModelCatalog.prettyName). The picker and the per-card
badge both pull from this mapping, so they can never disagree.
- Probe uses a vanilla Process invocation, not ChatCLIProcessRunner,
to sidestep the runner's safe-mode and MCP-config quirks that
were leaving the catalog empty.
Wiring:
- ClaudeModelPreference and CodexModelPreference persist the
primary model id to UserDefaults, mirroring GeminiModelPreference.
- ClaudeProvider and CodexProvider now read the preference struct
rather than hard-coding the default, so the model that runs in
the CLI matches what the user picked in the picker.
- LLMService.providerModelId(for: .claude/.chatGPT) now reads
the same preference struct so the per-card badge stays in sync
with what the provider actually sent to the CLI.
Claude Code 2.1.169+ added --safe-mode; 2.1.195+ added --effort; 2.1.191+ added --prompt-suggestions; --name is a subagent-only flag. Dayflow was passing all four unconditionally, so users on Claude Code 2.1.22 (January 2026, the version most users install first) saw every recording batch fail with 'error: unknown option --safe-mode' (or --name / --effort / --prompt-suggestions).
- Remove --safe-mode, --name, --prompt-suggestions from the cmdline
- Replace --effort with --settings JSON ('effortLevel' key) — universal across versions
- Add ClaudeCapabilityProbe that runs 'claude --version' once per process and gates --safe-mode on >= 2.1.169
- Defense in depth: --tools Read + --allowedTools still restrict the read scope, so older Claude Code versions stay sandboxed without the --safe-mode flag
Closes the Dayflow-wide batch-failure pattern on Claude Code < 2.1.169.
This PR combines 3 separate fixes that were previously submitted as JerryZLiu#347, JerryZLiu#348, JerryZLiu#349 (all closed because GitHub does not allow 2+ open PRs from the same (head, base) tuple). The 3 fixes are: * fix(chat-cli): version-aware Claude CLI flag handling. Claude Code 2.1.169+ supports --safe-mode, --prompt-suggestions, and --effort; 2.1.22 (the version most users install first) rejects them with "unknown option". The new ClaudeCapabilityProbe gates --safe-mode on the installed version, and the --effort flag was retired in favor of --settings "{\"effortLevel\":\"low\"}" JSON injection, which works on all Claude Code versions. * fix(daily): wait for notification authorization probe before unlocking. The DailyView.onAppear fired checkNotificationAuthorizationForUnlock() asynchronously, and the seeded notificationAuthorizationStatus was .notDetermined, so users who already had notifications enabled in System Settings could still be sent to the onboarding step. startDailyAccessFlow now awaits the probe so canUnlockDaily returns the right value, and a re-checked "didOnboard" UserDefaults flag gates the permission notice. * fix(transcribe): surface transient Claude API failures with clear messaging. Claude's --output-format json surfaces API errors (529 overloaded, rate_limit, 5xx server) as JSON events on stdout, not stderr, but the recording panel only inspected stderr and showed a generic "Claude CLI exited with code 1". The new detectTransientClaudeFailure() pattern-matches stdout for 5xx/rate_limit and shows a "Claude is busy, please wait" message instead. All 3 fixes are rebased onto Dayflow v2.1.0 (upstream main @ 8944d27, 47 new commits). Conflicts resolved in ChatCLIProcessRunner.swift, ClaudeProvider.swift, ClaudeProvider+Transcription.swift, ClaudeProvider+ActivityCards.swift, StorageManager+TimelineCards.swift, the test files, and project.pbxproj (DEVELOPMENT_TEAM = 2HPG9L9JZ7 kept for the personal Apple Dev account). Tested with Claude Code 2.1.22.
Local debug builds use ad-hoc signing by default, but ad-hoc signed macOS binaries can't display the UNUserNotificationCenter requestAuthorization prompt — the user has to manually sign with an Apple Development certificate to unlock notification-driven UI (Daily BETA, journal reminders). Pin DEVELOPMENT_TEAM to the user's 2HPG9L9JZ7 team so xcodebuild auto-signing can produce a working prompt surface. The team's bundle ID is unchanged; only the team that issues the signing certificate is different. Re-pin to L75WYD8X4Y when building for App Store / TestFlight distribution.
The rebase conflict resolution on LLMService.swift kept the usage of `activeProviderId` and `activeModelId` at lines 997-998 (passed to TimelineCardShell as providerId/modelId), but the variable declarations were lost during the conflict resolution. This commit adds them back, derived from `activeContext`: ``` let activeProviderId = activeContext.id.rawValue let activeModelId = providerModelId(for: activeContext.id) ``` `activeContext` is a TimelineProviderContext returned by the provider dispatch in the do-block above; `activeContext.id` is the LLMProviderID that actually generated this batch of cards (may differ from `primaryProviderID` when the backup/Gemma fallback was used). `providerModelId(for:)` (defined at line 198) returns the model identifier string for a given provider (e.g. "claude-sonnet-4-5", "gemini-2.5-pro"), or nil if not applicable. The previous rebase conflict resolution noted: > LLMService.swift: kept upstream `cardReplacementStartTime/EndTime` helpers The activeProviderId/activeModelId lines from the `feat(ai): surface provider and model on each generated card` commit (the rebase input) were preserved, but their declarations at the top of the relevant scope were not. This restores them so the build compiles. Tested: `xcodebuild -configuration Release` succeeds, .app installs to /Applications/Dayflow.app (94MB, Apple Dev signed), launches without crash.
Ports the MiniMax integration from JerryZLiu#320 onto the v2.0.3 base, which refactored the provider system into LLMProviderID with separate cases for chatGPT, claude, openAICompatible, and local. What's included - New LLMProviderID.minimax case with analytics + display label - MiniMaxProvider + 4 extension files (Networking, Reasoning, Summaries, Transcription) following the OllamaProvider pattern but using Bearer auth against https://api.minimax.io/v1 with M3's 1M-token context - MiniMaxModelPreference, MiniMaxPromptPreferences, MiniMaxAPIHelper - LLMService.makeMiniMaxProvider() wired into makeBatchProvider and makeTextProvider; providerModelId(for:) helper stamps cards with the active model - Storage layer: provider_id + model_id columns on timeline_cards with ALTER TABLE migration, TimelineCard/TimelineCardShell fields, and fetchAllTimelineCards() for the dashboard - Onboarding: 6th provider card (sparkles icon) + dedicated 4-step setup flow (get-key, enter-key, test, complete) - Settings: MiniMax section in providers tab with model picker, keychain field, test-connection button, and prompt-override plumbing - ModelCatalog + ProviderStatsCalculator + ProviderStatsView (dashboard data + GitHub-style heatmap + daily/weekly/monthly breakdowns) Build verified: xcodebuild Release succeeds, no new errors vs v2.0.3 base
M3NT1
force-pushed
the
feat/minimax-m3-provider-v2
branch
from
August 14, 2026 05:06
27c3c68 to
6d3d0c3
Compare
Author
Rebased onto current main (v2.1.0)This PR has been rebased onto the current What changed in the rebase
DependencyThis PR now depends on #347 being merged first. The MiniMax provider uses the |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
This PR is a re-port of the MiniMax M3 provider integration onto the current
main(v2.0.3) base, which refactored the provider system intoLLMProviderIDwith separate cases forchatGPT,claude,openAICompatible, andlocal. It also brings over the model-management + usage-analytics layer that was in the original PR #320.The user's request:
Why a new branch?
PR #320 was opened against the v2.0.0 base. Since then upstream has landed 163 commits — including a major provider refactor that split
chatGPTClaudeinto separatechatGPT/claudecases, introducedopenAICompatibleandlocalcases, addedLLMProviderSetupPreferences, and rebuilt onboarding/settings around a newProviderSetupStatemachine.A straight rebase produced 21 conflicting files; rather than fight through them, this branch is a clean re-application onto the v2.0.3 base so both upstream's evolution and our MiniMax additions land together. #320 should be closed in favour of this PR.
Changes
Core/AI/LLMTypes.swift— added.minimaxcase toLLMProviderIDwith analytics + display label.Core/AI/MiniMaxProvider*.swift(8 new files) — the provider itself, modelled onOllamaProviderbut with Bearer auth againsthttps://api.minimax.io/v1and a 1M-token context window. Five files: main + Networking, Reasoning (strips<think>…</think>defensively), Summaries, Transcription. PlusMiniMaxModelPreference,MiniMaxPromptPreferences, andUtilities/MiniMaxAPIHelper.Core/AI/ModelCatalog.swift(new) — actor that fetches the current model list from each provider's API on the fly:GET /v1/modelsfor MiniMax (OpenAI-compatible)GET /v1beta/models?key=…for Gemini (filtered to vision-capable)GET {baseURL}/api/tagsfor Ollama,GET /v1/modelsfor LM StudioUserDefaults. On failure, falls back to a curated list with a banner in the UI.Core/AI/LLMService.swift— newmakeMiniMaxProvider()wired into bothmakeBatchProviderandmakeTextProviderswitch statements. A newproviderModelId(for:)helper centralises the per-provider model lookup. A newattachMetahelper stamps every card before persistence.Core/Analytics/ProviderStatsCalculator.swift(new) — pure data layer that turns the localTimelineCardlist into aProviderStatsSummary:byProvider: [String: Int](always includes all known providers, even at zero)byModel: [(providerID, modelID, count)]daily: [DailyStat](last 365 days)weekly: [WeeklyStat](last 26 weeks)monthly: [MonthlyStat](last 12 months)heatmapDays: [HeatmapDay](last 365 days, intensity 0…1)Views/UI/Dashboard/ProviderStatsView.swift(new) — self-contained dashboard surface with:Week / Month / Quarter / Yearrange pickerStorage layer —
provider_id+model_idcolumns ontimeline_cardswithALTER TABLEmigration + composite index.TimelineCardandTimelineCardShellgained matching fields. NewStorageManager.fetchAllTimelineCards()powers the dashboard.Onboarding — 6th provider card on the comparison screen (sparkles icon, "MiniMax M3", positioned between OpenAI-compatible and Local). New 4-step setup flow: get-key → enter-key → test-connection → complete. Reuses the existing
apiKeystate field that the input view binds to.Settings → Providers tab — MiniMax section with model picker (live or curated), keychain API-key field, test-connection button that re-opens the provider-setup modal. Prompt-override plumbing added for symmetry with the other providers.
Per-card badge —
CanvasActivityCard(and equivalent list/grid renderers) now show a small text pill below the time label, e.g.MiniMax · MiniMax-M3,Claude · Sonnet 4.5,Local · qwen3-vl:4b. Falls back to "Unknown" when the card predates the schema migration.ChatCLIProvider— gained adefaultModel: String?parameter that overrides the previously hard-coded"sonnet"/"haiku"/"gpt-5.4"strings.LLMService.makeChatCLIProviderreadsllmCodexModel/llmClaudeModelfromUserDefaultsand passes them through.Privacy
https://api.minimax.io/v1/chat/completionsand nowhere elseLLMLogger, but no PIIBuild verification
The pre-existing Swift 6 / Sendable warnings in
ChatCLIProcessRunner.swift,MiniMaxProvider+Transcription.swift, etc. are unchanged from upstream and not introduced by this PR.