Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 0.29.2 — Unreleased

### Fixed
- StepFun: refresh expired Oasis tokens and persist recovered manual sessions. Thanks @LeoLin990405!
- Release: prevent manual CLI artifact builds from publishing or clobbering release assets (#1154). Thanks @jskoiz!
- Cost history: route OpenAI and Mistral API spend through the shared cost-history cards, including OpenAI request counts (#1163). Thanks @LeoLin990405!
- Alibaba Token Plan: update usage refreshes to the Bailian subscription-summary endpoint (#1142). Thanks @YanxinXue!
Expand Down
7 changes: 7 additions & 0 deletions Sources/CodexBar/ProviderRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ struct ProviderRegistry {
token: token)
}
},
providerManualTokenUpdater: { provider, token in
await MainActor.run {
if provider == .stepfun {
settings.stepfunToken = token
}
}
},
costUsageHistoryDays: settings.costUsageHistoryDays)
})
specs[provider] = spec
Expand Down
7 changes: 7 additions & 0 deletions Sources/CodexBar/UsageStore+TokenAccounts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,13 @@ extension UsageStore {
token: token)
}
},
providerManualTokenUpdater: { [weak settings = self.settings] provider, token in
await MainActor.run {
if provider == .stepfun {
settings?.stepfunToken = token
}
}
},
costUsageHistoryDays: self.settings.costUsageHistoryDays)
}

Expand Down
5 changes: 4 additions & 1 deletion Sources/CodexBarCLI/CLIUsageCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,10 @@ extension CodexBarCLI {
settings: settings,
fetcher: tokenContext.fetcher(base: command.fetcher, provider: provider, env: env),
claudeFetcher: command.claudeFetcher,
browserDetection: command.browserDetection)
browserDetection: command.browserDetection,
selectedTokenAccountID: account?.id,
tokenAccountTokenUpdater: tokenContext.tokenUpdater(for: account),
providerManualTokenUpdater: tokenContext.manualTokenUpdater())
let outcome = await Self.fetchProviderUsage(
provider: provider,
context: fetchContext)
Expand Down
76 changes: 75 additions & 1 deletion Sources/CodexBarCLI/TokenAccountCLI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ struct TokenAccountCLIContext {
return self.makeSnapshot(
stepfun: ProviderSettingsSnapshot.StepFunProviderSettings(
cookieSource: cookieSource,
manualToken: cookieHeader ?? "",
manualToken: self.stepfunManualToken(account: account, config: config),
username: config?.sanitizedAPIKey ?? "",
password: ""))
default:
Expand Down Expand Up @@ -346,6 +346,68 @@ struct TokenAccountCLIContext {
return env
}

func tokenUpdater(for account: ProviderTokenAccount?) -> ProviderFetchContext.TokenAccountTokenUpdater? {
guard let account else { return nil }
return { provider, accountID, token in
guard accountID == account.id else { return }
try? Self.updateStoredTokenAccount(provider: provider, accountID: accountID, token: token)
}
}

func manualTokenUpdater() -> ProviderFetchContext.ProviderManualTokenUpdater {
{ provider, token in
try? Self.updateStoredManualToken(provider: provider, token: token)
}
}

private static func updateStoredManualToken(provider: UsageProvider, token: String) throws {
guard provider == .stepfun else { return }
let trimmed = token.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return }

let store = CodexBarConfigStore()
var config = try store.load() ?? .makeDefault()
var providerConfig = config.providerConfig(for: provider) ?? ProviderConfig(id: provider)
providerConfig.region = trimmed
config.setProviderConfig(providerConfig)
try store.save(config)
}

private static func updateStoredTokenAccount(
provider: UsageProvider,
accountID: UUID,
token: String) throws
{
let trimmed = token.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return }

let store = CodexBarConfigStore()
guard var config = try store.load() else { return }
guard var providerConfig = config.providerConfig(for: provider),
let data = providerConfig.tokenAccounts,
let index = data.accounts.firstIndex(where: { $0.id == accountID })
else {
return
}

let existing = data.accounts[index]
var accounts = data.accounts
accounts[index] = ProviderTokenAccount(
id: existing.id,
label: existing.label,
token: trimmed,
addedAt: existing.addedAt,
lastUsed: existing.lastUsed,
externalIdentifier: existing.externalIdentifier,
organizationID: existing.organizationID)
providerConfig.tokenAccounts = ProviderTokenAccountData(
version: data.version,
accounts: accounts,
activeIndex: data.clampedActiveIndex())
config.setProviderConfig(providerConfig)
try store.save(config)
}

func fetcher(base: UsageFetcher, provider: UsageProvider, env: [String: String]) -> UsageFetcher {
guard provider == .codex else { return base }
return UsageFetcher(environment: env)
Expand Down Expand Up @@ -487,12 +549,24 @@ struct TokenAccountCLIContext {
return .manual
}
if let override = config?.cookieSource { return override }
if provider == .stepfun, config?.sanitizedRegion != nil {
return .manual
}
if config?.sanitizedCookieHeader != nil {
return .manual
}
return .auto
}

private func stepfunManualToken(account: ProviderTokenAccount?, config: ProviderConfig?) -> String {
if let account,
let support = TokenAccountSupportCatalog.support(for: .stepfun)
{
return TokenAccountSupportCatalog.normalizedCookieHeader(account.token, support: support)
}
return config?.sanitizedRegion ?? config?.sanitizedCookieHeader ?? ""
}

private func resolveZaiRegion(_ config: ProviderConfig?) -> ZaiAPIRegion {
guard let raw = config?.region?.trimmingCharacters(in: .whitespacesAndNewlines),
!raw.isEmpty
Expand Down
4 changes: 4 additions & 0 deletions Sources/CodexBarCore/Providers/ProviderFetchPlan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public enum ProviderSourceMode: String, CaseIterable, Sendable, Codable {

public struct ProviderFetchContext: Sendable {
public typealias TokenAccountTokenUpdater = @Sendable (UsageProvider, UUID, String) async -> Void
public typealias ProviderManualTokenUpdater = @Sendable (UsageProvider, String) async -> Void

public let runtime: ProviderRuntime
public let sourceMode: ProviderSourceMode
Expand All @@ -34,6 +35,7 @@ public struct ProviderFetchContext: Sendable {
public let browserDetection: BrowserDetection
public let selectedTokenAccountID: UUID?
public let tokenAccountTokenUpdater: TokenAccountTokenUpdater?
public let providerManualTokenUpdater: ProviderManualTokenUpdater?
public let costUsageHistoryDays: Int

public init(
Expand All @@ -51,6 +53,7 @@ public struct ProviderFetchContext: Sendable {
browserDetection: BrowserDetection,
selectedTokenAccountID: UUID? = nil,
tokenAccountTokenUpdater: TokenAccountTokenUpdater? = nil,
providerManualTokenUpdater: ProviderManualTokenUpdater? = nil,
costUsageHistoryDays: Int = 30)
{
self.runtime = runtime
Expand All @@ -67,6 +70,7 @@ public struct ProviderFetchContext: Sendable {
self.browserDetection = browserDetection
self.selectedTokenAccountID = selectedTokenAccountID
self.tokenAccountTokenUpdater = tokenAccountTokenUpdater
self.providerManualTokenUpdater = providerManualTokenUpdater
self.costUsageHistoryDays = max(1, min(365, costUsageHistoryDays))
}
}
Expand Down
Loading