diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c212d0627d..ae4bb4498b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Settings: edit saved token-account labels and credentials in place and reorder accounts without changing the active account (#4612). Thanks @lghsigma597! ### Fixed +- OpenCode Go: honor the app's configured source and show the source that supplied usage (#4616). Thanks @ArietidsZ! - Pi: keep cache scope stable when the same session roots are discovered in a different order (#4618). Thanks @dhruva77! - Claude cost history: preserve the last valid cache when transcript roots or directories are unreadable (#4617). Thanks @dhruva77! - Codex: open the Usage Dashboard on the overview tab to avoid incorrect redirects (#4607). Thanks @Alex-Keyes! diff --git a/Sources/CodexBar/Providers/OpenCodeGo/OpenCodeGoProviderImplementation.swift b/Sources/CodexBar/Providers/OpenCodeGo/OpenCodeGoProviderImplementation.swift index 16a4a2a75f7..b93c29db0e5 100644 --- a/Sources/CodexBar/Providers/OpenCodeGo/OpenCodeGoProviderImplementation.swift +++ b/Sources/CodexBar/Providers/OpenCodeGo/OpenCodeGoProviderImplementation.swift @@ -6,7 +6,19 @@ struct OpenCodeGoProviderImplementation: ProviderImplementation { @MainActor func presentation(context _: ProviderPresentationContext) -> ProviderPresentation { - ProviderPresentation { _ in "web" } + ProviderPresentation { context in + context.store.sourceLabel(for: context.provider) + } + } + + @MainActor + func defaultSourceLabel(context: ProviderSourceLabelContext) -> String? { + context.settings.providerConfig(for: .opencodego)?.source?.rawValue + } + + @MainActor + func sourceMode(context: ProviderSourceModeContext) -> ProviderSourceMode { + context.settings.providerConfig(for: .opencodego)?.source ?? .auto } @MainActor diff --git a/Sources/CodexBar/Providers/OpenCodeGo/OpenCodeGoSettingsStore.swift b/Sources/CodexBar/Providers/OpenCodeGo/OpenCodeGoSettingsStore.swift index 9cd1d7f4411..cc528135eda 100644 --- a/Sources/CodexBar/Providers/OpenCodeGo/OpenCodeGoSettingsStore.swift +++ b/Sources/CodexBar/Providers/OpenCodeGo/OpenCodeGoSettingsStore.swift @@ -4,13 +4,7 @@ import Foundation extension SettingsStore { var opencodegoWorkspaceID: String { get { self.configSnapshot.providerConfig(for: .opencodego)?.workspaceID ?? "" } - set { - let trimmed = newValue.trimmingCharacters(in: .whitespacesAndNewlines) - let value = trimmed.isEmpty ? nil : trimmed - self.updateProviderConfig(provider: .opencodego) { entry in - entry.workspaceID = value - } - } + set { self[providerConfig: .opencodego, field: .workspace] = newValue } } var opencodegoCookieHeader: String { @@ -34,20 +28,10 @@ extension SettingsStore { { let cookieSettings: ProviderSettingsSnapshot.CookieProviderSettings = self.resolvedCookieSettings( provider: .opencodego, - configuredSource: self.opencodegoCookieSource, - configuredHeader: self.opencodegoCookieHeader, tokenOverride: tokenOverride) return ProviderSettingsSnapshot.OpenCodeProviderSettings( cookieSource: cookieSettings.cookieSource, manualCookieHeader: cookieSettings.manualCookieHeader, - workspaceID: self.opencodegoSnapshotWorkspaceID) - } - - private var opencodegoSnapshotWorkspaceID: String? { - guard let workspaceID = self.configSnapshot.providerConfig(for: .opencodego)?.workspaceID else { - return nil - } - let trimmed = workspaceID.trimmingCharacters(in: .whitespacesAndNewlines) - return trimmed.isEmpty ? nil : trimmed + workspaceID: self.providerConfig(for: .opencodego)?.sanitizedWorkspaceID) } } diff --git a/Tests/CodexBarTests/OpenCodeGoAppSourceTests.swift b/Tests/CodexBarTests/OpenCodeGoAppSourceTests.swift new file mode 100644 index 00000000000..c48d5157584 --- /dev/null +++ b/Tests/CodexBarTests/OpenCodeGoAppSourceTests.swift @@ -0,0 +1,49 @@ +import CodexBarCore +import Testing +@testable import CodexBar + +@MainActor +@Suite(.serialized) +struct OpenCodeGoAppSourceTests { + @Test(arguments: [ProviderSourceMode.auto, .api, .web]) + func `app honors configured source even when manual cookies exist`(source: ProviderSourceMode) async throws { + let fixture = try ProviderSettingsDescriptorTests().makeSettingsFixture( + suite: "OpenCodeGoAppSourceTests-\(source.rawValue)") + fixture.settings.updateProviderConfig(provider: .opencodego) { config in + config.source = source + config.cookieSource = .manual + config.cookieHeader = "Cookie: invalid" + config.apiKey = "fixture-api-key" + } + + let resolved = ProviderRegistry.resolvedSourceMode( + provider: .opencodego, settings: fixture.settings, account: nil) + #expect(resolved == source) + let spec = try #require(fixture.store.providerSpecs[.opencodego]) + let fetchContext = spec.makeFetchContext() + #expect(fetchContext.sourceMode == source) + #expect(fetchContext.settings?.opencodego?.cookieSource == .manual) + let strategies = await spec.descriptor.fetchPlan.pipeline.resolveStrategies(fetchContext) + if source == .api { + #expect(strategies.map(\.id) == ["opencodego.api"]) + } else if source == .web { + #expect(strategies.map(\.id) == ["opencodego.web"]) + } else { + #expect(strategies.map(\.id) == ["opencodego.web", "opencodego.local", "opencodego.api"]) + } + } + + @Test + func `settings header shows configured or last successful source instead of web`() throws { + let fixture = try ProviderSettingsDescriptorTests().makeSettingsFixture( + suite: "OpenCodeGoAppSourceTests-header") + fixture.settings.updateProviderConfig(provider: .opencodego) { $0.source = .api } + let context = fixture.presentationContext( + provider: .opencodego, metadata: OpenCodeGoProviderDescriptor.descriptor.metadata) + let presentation = OpenCodeGoProviderImplementation().presentation(context: context) + + #expect(presentation.detailLine(context) == "api") + fixture.store.lastSourceLabels[.opencodego] = "local+api" + #expect(presentation.detailLine(context) == "local+api") + } +} diff --git a/docs/opencode.md b/docs/opencode.md index af7072a6729..8463596bfab 100644 --- a/docs/opencode.md +++ b/docs/opencode.md @@ -102,6 +102,7 @@ usage is a separate [OpenAI provider](openai.md), not Codex subscription quota. overlays authoritative API windows when an API key is configured, then falls back through the API and legacy web sources when local history is unavailable. Auto stays web-first when a Cookie token account, manual cookie, or workspace override scopes the request, because local history is device-wide. +- The app honors an explicit API or Web source even when a manual Cookie is configured. Its source label shows the configured source before a successful fetch, then the source that actually supplied the result. - The local monthly window is an estimate anchored at the earliest local row and can drift from the real billing cycle. The local strategy prefers API-reported rolling/weekly/monthly percentages and reset timestamps. When no API key is configured, a cached, manual, or automatically discovered session can overlay Console quotas (plus Zen