-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat: add Muse (Meta) provider #2936
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
Open
toml0006
wants to merge
17
commits into
steipete:main
Choose a base branch
from
toml0006:feat/muse-provider
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
3bc93d6
feat: add Muse (Meta) provider
toml0007 bef0acf
fix: update gatekeeper fingerprints for muse provider
toml0007 8d65052
fix: address ClawSweeper P1/P2 — muse renderer + auth error + https g…
toml0007 c4fb7ff
fix: gatekeeper — update allowlisted cluster for muse insertion
toml0007 be4ca11
fix: reject malformed models payload (empty {})
toml0007 55ffd89
fix: address ClawSweeper P2s — currency-only + focused tests
toml0007 372bcf0
fix: contribution guide — provider counts, lint, gatekeeper
toml0007 0b7826e
feat(muse): add web Team usage via dev.meta.ai cookies (congruent web…
toml0007 e0b412c
fix(muse): correct SweetCookieKit wiring - BrowserCookieQuery + codex…
toml0007 2cb9413
muse: switch web fetcher to dev.meta.ai GraphQL LLMDCUsageQuery
toml0007 df97861
muse: include facebook.com cookies for Comet auth
toml0007 6453493
muse: robust team_id extraction for Safari/Comet HTML
toml0007 bd4cc47
muse: clean debug logging
toml0007 ddc68d3
muse: parse LLMD-C team metrics (requests/tokens/cost)
toml0007 d6625fa
muse: fallback DTSG/LSD for manual llm_sess header
toml0007 a8a8f31
muse: add Daily usage detail history (7-day)
toml0007 05aa359
muse: localize token/cost/count rendering
toml0007 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
70 changes: 70 additions & 0 deletions
70
Sources/CodexBar/Providers/Muse/MuseProviderImplementation.swift
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import AppKit | ||
| import CodexBarCore | ||
| import Foundation | ||
| import SwiftUI | ||
|
|
||
| struct MuseProviderImplementation: ProviderImplementation { | ||
| let id: UsageProvider = .muse | ||
|
|
||
| @MainActor | ||
| func presentation(context _: ProviderPresentationContext) -> ProviderPresentation { | ||
| ProviderPresentation { _ in "api" } | ||
| } | ||
|
|
||
| @MainActor | ||
| func observeSettings(_ settings: SettingsStore) { | ||
| _ = settings.museAPIToken | ||
| _ = settings.museBaseURL | ||
| } | ||
|
|
||
| @MainActor | ||
| func settingsSnapshot(context: ProviderSettingsSnapshotContext) | ||
| -> ProviderSettingsSnapshotContribution? | ||
| { | ||
| .muse(context.settings.museSettingsSnapshot()) | ||
| } | ||
|
|
||
| @MainActor | ||
| func isAvailable(context: ProviderAvailabilityContext) -> Bool { | ||
| if MuseSettingsReader.apiKey(environment: context.environment) != nil { | ||
| return true | ||
| } | ||
| context.settings.ensureMuseAPITokenLoaded() | ||
| return context.settings.hasMuseAPIToken | ||
| } | ||
|
|
||
| @MainActor | ||
| func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] { | ||
| [ | ||
| ProviderSettingsFieldDescriptor( | ||
| id: "muse-api-key", | ||
| title: "Meta Muse API key", | ||
| subtitle: "API key from ai.developer.meta.com. Also accepts META_API_KEY.", | ||
| kind: .secure, | ||
| placeholder: "sk-...", | ||
| binding: context.stringBinding(\.museAPIToken), | ||
| actions: [ | ||
| ProviderSettingsActionDescriptor( | ||
| id: "muse-open-dashboard", | ||
| title: "Open Meta developer console", | ||
| style: .link, | ||
| isVisible: nil, | ||
| perform: { | ||
| NSWorkspace.shared.open(URL(string: "https://ai.developer.meta.com/")!) | ||
| }), | ||
| ], | ||
| isVisible: nil, | ||
| onActivate: { context.settings.ensureMuseAPITokenLoaded() }), | ||
| ProviderSettingsFieldDescriptor( | ||
| id: "muse-base-url", | ||
| title: "API base URL (optional)", | ||
| subtitle: "Override for self-hosted or proxy. Default: https://api.meta.ai", | ||
| kind: .plain, | ||
| placeholder: "https://api.meta.ai", | ||
| binding: context.stringBinding(\.museBaseURL), | ||
| actions: [], | ||
| isVisible: nil, | ||
| onActivate: nil), | ||
| ] | ||
| } | ||
| } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import CodexBarCore | ||
| import Foundation | ||
|
|
||
| extension SettingsStore { | ||
| var museAPIToken: String { | ||
| get { | ||
| guard let config = self.configSnapshot.providerConfig(for: .muse) else { return "" } | ||
| return config.sanitizedAPIKey ?? "" | ||
| } | ||
| set { | ||
| self.updateProviderConfig(provider: .muse) { entry in | ||
| entry.apiKey = self.normalizedConfigValue(newValue) | ||
| } | ||
| self.logSecretUpdate(provider: .muse, field: "apiKey", value: newValue) | ||
| } | ||
| } | ||
|
|
||
| var museBaseURL: String { | ||
| get { | ||
| guard let config = self.configSnapshot.providerConfig(for: .muse) else { return "" } | ||
| return config.sanitizedBaseURL ?? "" | ||
| } | ||
| set { | ||
| self.updateProviderConfig(provider: .muse) { entry in | ||
| entry.baseURL = self.normalizedConfigValue(newValue) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func ensureMuseAPITokenLoaded() {} | ||
|
|
||
| var hasMuseAPIToken: Bool { | ||
| guard let config = self.configSnapshot.providerConfig(for: .muse) else { return false } | ||
| return config.sanitizedAPIKey != nil | ||
| } | ||
|
|
||
| var configuredMuseBaseURL: String? { | ||
| guard let raw = self.configSnapshot.providerConfig(for: .muse)?.baseURL? | ||
| .trimmingCharacters(in: .whitespacesAndNewlines), | ||
| !raw.isEmpty | ||
| else { | ||
| return nil | ||
| } | ||
| return raw | ||
| } | ||
| } | ||
|
|
||
| extension SettingsStore { | ||
| func museSettingsSnapshot() -> ProviderSettingsSnapshot.MuseProviderSettings { | ||
| ProviderSettingsSnapshot.MuseProviderSettings(baseURL: self.configuredMuseBaseURL) | ||
| } | ||
| } |
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
119 changes: 119 additions & 0 deletions
119
Sources/CodexBarCore/Providers/Muse/MuseProviderDescriptor.swift
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| import Foundation | ||
|
|
||
| public enum MuseProviderDescriptor { | ||
| public static let descriptor: ProviderDescriptor = Self.makeDescriptor() | ||
|
|
||
| private static let credentials = ProviderCredentialAdapter.apiKey( | ||
| environmentKey: MuseSettingsReader.apiKeyEnvironmentKeys[0], | ||
| precedence: .environment, | ||
| environmentHasValue: { MuseSettingsReader.apiKey(environment: $0) != nil }, | ||
| resolve: { env in MuseSettingsReader.apiKey(environment: env) }, | ||
| missingCredentialMessage: { _ in MuseUsageError.missingCredentials.errorDescription ?? "Missing Muse API key" } | ||
| ) | ||
|
|
||
| static func makeDescriptor() -> ProviderDescriptor { | ||
| ProviderDescriptor( | ||
| id: .muse, | ||
| settingsSection: .init(MuseProviderSettingsKey.self, credentialSettings: { context in | ||
| MuseProviderSettings(baseURL: context.config?.sanitizedBaseURL) | ||
| }), | ||
| credentials: self.credentials, | ||
| metadata: ProviderMetadata( | ||
| id: .muse, | ||
| displayName: "Muse", | ||
| shortDisplayName: "Muse", | ||
| sessionLabel: "Balance", | ||
| weeklyLabel: "Balance", | ||
| opusLabel: nil, | ||
| supportsOpus: false, | ||
| supportsCredits: false, | ||
| creditsHint: "", | ||
| toggleTitle: "Show Muse (Meta) usage", | ||
| cliName: "muse", | ||
| defaultEnabled: false, | ||
| widgetSelectable: false, | ||
| isPrimaryProvider: false, | ||
| usesAccountFallback: false, | ||
| balanceOnly: true, | ||
| browserCookieOrder: nil, | ||
| dashboardURL: "https://ai.developer.meta.com/", | ||
| statusPageURL: nil, | ||
| statusLinkURL: nil | ||
| ), | ||
| branding: ProviderBranding( | ||
| iconStyle: .init(provider: .muse), | ||
| iconResourceName: "ProviderIcon-muse", | ||
| color: ProviderColor(red: 0.0 / 255, green: 100 / 255, blue: 224 / 255), | ||
| confettiPalette: [ | ||
| ProviderColor(hex: 0x0064E0), | ||
| ProviderColor(hex: 0x0469FF), | ||
| ProviderColor(hex: 0x7B61FF), | ||
| ], | ||
| widgetColor: ProviderColor(red: 0.0 / 255, green: 100 / 255, blue: 224 / 255) | ||
| ), | ||
| tokenCost: ProviderTokenCostConfig( | ||
| supportsTokenCost: false, | ||
| noDataMessage: { "Muse cost history is not available via API. Billing is pay-as-you-go at $1.25 / $4.25 per 1M tokens." } | ||
| ), | ||
| presentation: ProviderUsagePresentation( | ||
| planRow: ProviderPlanRowPresentation(label: "Balance", stripsBalancePrefix: true) | ||
| ), | ||
| fetchPlan: ProviderFetchPlan( | ||
| sourceModes: [.auto, .api], | ||
| pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [MuseAPIFetchStrategy()] }) | ||
| ), | ||
| cli: ProviderCLIConfig( | ||
| name: "muse", | ||
| aliases: ["meta", "metamuse"], | ||
| versionDetector: nil | ||
| ) | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| struct MuseAPIFetchStrategy: ProviderFetchStrategy { | ||
| let id = "muse.api" | ||
| let kind: ProviderFetchKind = .apiToken | ||
| private let transport: any ProviderHTTPTransport | ||
|
|
||
| init(transport: any ProviderHTTPTransport = ProviderHTTPClient.shared) { | ||
| self.transport = transport | ||
| } | ||
|
|
||
| func isAvailable(_ context: ProviderFetchContext) async -> Bool { | ||
| MuseSettingsReader.apiKey(environment: context.env) != nil | ||
| } | ||
|
|
||
| func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult { | ||
| guard let apiKey = MuseSettingsReader.apiKey(environment: context.env) else { | ||
| throw MuseUsageError.missingCredentials | ||
| } | ||
|
|
||
| // Prefer config baseURL (via settings), then env, then default | ||
| let baseURL: String? = context.settings?.muse?.baseURL ?? MuseSettingsReader.baseURL(environment: context.env) | ||
|
|
||
| let usage = try await MuseUsageFetcher.fetchUsage( | ||
| apiKey: apiKey, | ||
| baseURLString: baseURL, | ||
| session: self.transport | ||
| ) | ||
| return self.makeResult(usage: usage.toUsageSnapshot(), sourceLabel: "api") | ||
| } | ||
|
|
||
| func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool { | ||
| false | ||
| } | ||
| } | ||
|
|
||
| // MARK: - ProviderConfig extension for baseURL | ||
|
|
||
| extension ProviderConfig { | ||
| public var baseURL: String? { | ||
| get { self.extensionValue(forKey: "baseURL") } | ||
| set { self.setExtensionValue(newValue, forKey: "baseURL") } | ||
| } | ||
|
|
||
| public var sanitizedBaseURL: String? { | ||
| Self.clean(self.baseURL) | ||
| } | ||
| } | ||
31 changes: 31 additions & 0 deletions
31
Sources/CodexBarCore/Providers/Muse/MuseProviderSettings.swift
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import Foundation | ||
|
|
||
| public struct MuseProviderSettings: Sendable { | ||
| public let baseURL: String? | ||
|
|
||
| public init(baseURL: String? = nil) { | ||
| self.baseURL = baseURL | ||
| } | ||
| } | ||
|
|
||
| public enum MuseProviderSettingsKey: ProviderSettingsSectionKey { | ||
| public static let providerID = ProviderInstanceID.muse | ||
| public typealias Section = MuseProviderSettings | ||
| } | ||
|
|
||
| extension ProviderSettingsSnapshot { | ||
| public typealias MuseProviderSettings = CodexBarCore.MuseProviderSettings | ||
| public var muse: MuseProviderSettings? { | ||
| self[MuseProviderSettingsKey.self] | ||
| } | ||
|
|
||
| public static func make(muse: MuseProviderSettings?) -> Self { | ||
| self.make(muse, for: MuseProviderSettingsKey.self) | ||
| } | ||
| } | ||
|
|
||
| extension ProviderSettingsSnapshotContribution { | ||
| public static func muse(_ section: MuseProviderSettings) -> Self { | ||
| Self(section, for: MuseProviderSettingsKey.self) | ||
| } | ||
| } |
30 changes: 30 additions & 0 deletions
30
Sources/CodexBarCore/Providers/Muse/MuseSettingsReader.swift
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import Foundation | ||
|
|
||
| public enum MuseSettingsReader: Sendable { | ||
| public static let apiKeyEnvironmentKeys = ["MUSE_API_KEY", "META_API_KEY", "META_MUSE_API_KEY"] | ||
| public static let baseURLEnvironmentKey = "MUSE_API_URL" | ||
|
|
||
| public static func apiKey(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? { | ||
| for key in apiKeyEnvironmentKeys { | ||
| if let value = cleaned(environment[key]) { | ||
| return value | ||
| } | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| public static func baseURL(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? { | ||
| cleaned(environment[baseURLEnvironmentKey]) | ||
| } | ||
|
|
||
| private static func cleaned(_ raw: String?) -> String? { | ||
| guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else { | ||
| return nil | ||
| } | ||
| if (value.hasPrefix("\"") && value.hasSuffix("\"")) || (value.hasPrefix("'") && value.hasSuffix("'")) { | ||
| value = String(value.dropFirst().dropLast()) | ||
| } | ||
| value = value.trimmingCharacters(in: .whitespacesAndNewlines) | ||
| return value.isEmpty ? nil : value | ||
| } | ||
| } |
Oops, something went wrong.
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.
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.
Setting
balanceOnlyhere adds.musetodescriptors.filter(\.metadata.balanceOnly), butProviderArchitectureGatekeeperTests.swift:196-198still asserts the exact set without Muse, so the full test suite will deterministically fail. Update that fixture and add focused Muse coverage alongside the new provider.AGENTS.md reference: AGENTS.md:L20-L24
Useful? React with 👍 / 👎.