Skip to content

Commit 0a644dc

Browse files
author
Peter Steinberger
committed
Merge remote-tracking branch 'origin/main' into pry-2622
2 parents 613af4c + 7002b57 commit 0a644dc

41 files changed

Lines changed: 528 additions & 189 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Added
66
- Kimi: enrich Code API and CLI usage with the monthly membership pool from a signed-in Kimi Desktop session, using WAL-safe read-only cookie access (#2351). Thanks @Leehow!
7+
- Kimi/GLM: distinguish Kimi Code from the regional Open Platform, bind China and international keys to their issuing hosts, and show GLM Coding Plan's 5-hour window as primary with MCP separate (#2351). Thanks @Leehow!
78

89
### Changed
910
- Menu: move each usage window's used percentage and reset time into its title row, with all pace detail on one line (#2182). Thanks @jack24254029!

Sources/CodexBar/Config/CodexBarConfigMigrator.swift

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ struct CodexBarConfigMigrator {
4040
// applyLegacyCookieSources reads only UserDefaults — cheap, runs unconditionally so
4141
// newly-added cookie-source keys are picked up on every launch.
4242
self.applyLegacyCookieSources(userDefaults: userDefaults, config: &config, state: &state)
43+
self.bindLegacyMoonshotAPIKeyRegion(config: &config, state: &state)
4344

4445
let migrationCompleted = userDefaults.bool(forKey: Self.legacyMigrationCompletedKey)
4546
if !migrationCompleted {
@@ -164,14 +165,27 @@ struct CodexBarConfigMigrator {
164165
}
165166
}
166167

168+
private static func bindLegacyMoonshotAPIKeyRegion(
169+
config: inout CodexBarConfig,
170+
state: inout MigrationState)
171+
{
172+
self.updateProvider(.moonshot, config: &config, state: &state) { entry in
173+
guard entry.sanitizedAPIKey != nil, entry.sanitizedAPIKeyRegion == nil else { return false }
174+
entry.apiKeyRegion = entry.sanitizedRegion ?? MoonshotRegion.international.rawValue
175+
return true
176+
}
177+
}
178+
167179
private static func migrateTokenProviders(
168180
_ providers: [(UsageProvider, () throws -> String?)],
169181
config: inout CodexBarConfig,
170182
state: inout MigrationState)
171183
{
172184
for (provider, loader) in providers {
173185
let token = try? loader()
174-
if token != nil { state.sawLegacySecrets = true }
186+
if token != nil {
187+
state.sawLegacySecrets = true
188+
}
175189
self.updateProvider(provider, config: &config, state: &state) { entry in
176190
self.setIfEmpty(&entry.apiKey, token)
177191
}
@@ -185,7 +199,9 @@ struct CodexBarConfigMigrator {
185199
{
186200
for (provider, loader) in providers {
187201
let header = try? loader()
188-
if header != nil { state.sawLegacySecrets = true }
202+
if header != nil {
203+
state.sawLegacySecrets = true
204+
}
189205
self.updateProvider(provider, config: &config, state: &state) { entry in
190206
self.setIfEmpty(&entry.cookieHeader, header)
191207
}
@@ -226,7 +242,9 @@ struct CodexBarConfigMigrator {
226242
if token?.isEmpty ?? true {
227243
token = userDefaults.string(forKey: "kimiManualCookieHeader")
228244
}
229-
if token != nil { state.sawLegacySecrets = true }
245+
if token != nil {
246+
state.sawLegacySecrets = true
247+
}
230248
self.updateProvider(.kimi, config: &config, state: &state) { entry in
231249
self.setIfEmpty(&entry.cookieHeader, token)
232250
}
@@ -239,7 +257,9 @@ struct CodexBarConfigMigrator {
239257
state: inout MigrationState)
240258
{
241259
let header = try? stores.opencodeCookieStore.loadCookieHeader()
242-
if header != nil { state.sawLegacySecrets = true }
260+
if header != nil {
261+
state.sawLegacySecrets = true
262+
}
243263
let workspaceID = userDefaults.string(forKey: "opencodeWorkspaceID")
244264
self.updateProvider(.opencode, config: &config, state: &state) { entry in
245265
var changed = false

Sources/CodexBar/MenuBarMetricWindowResolver.swift

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,27 +70,21 @@ enum MenuBarMetricWindowResolver {
7070
}
7171

7272
private static func tertiaryOrder(for provider: UsageProvider) -> [Lane] {
73-
if provider == .zai {
74-
return [.tertiary, .primary, .secondary]
75-
}
7673
if provider == .perplexity || provider == .cursor || provider == .antigravity {
7774
return [.tertiary, .secondary, .primary]
7875
}
7976
return [.primary, .secondary]
8077
}
8178

8279
private static func primaryOrder(for provider: UsageProvider) -> [Lane] {
83-
if provider == .zai {
84-
return [.primary, .tertiary, .secondary]
85-
}
8680
if provider == .perplexity || provider == .antigravity {
8781
return [.primary, .secondary, .tertiary]
8882
}
8983
return [.primary, .secondary]
9084
}
9185

9286
private static func secondaryOrder(for provider: UsageProvider) -> [Lane] {
93-
if provider == .zai || provider == .antigravity {
87+
if provider == .antigravity {
9488
return [.secondary, .primary, .tertiary]
9589
}
9690
if provider == .perplexity {
@@ -147,8 +141,8 @@ enum MenuBarMetricWindowResolver {
147141
if provider == .zai {
148142
return self.mostConstrainedWindow(
149143
primary: snapshot.primary,
150-
secondary: snapshot.tertiary,
151-
tertiary: nil) ?? snapshot.secondary
144+
secondary: snapshot.secondary,
145+
tertiary: nil)
152146
}
153147
if provider == .factory || provider == .kimi || provider == .litellm {
154148
if let exhausted = exhaustedWindow(

Sources/CodexBar/MenuCardView+ModelHelpers.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -821,9 +821,13 @@ extension UsageMenuCardView.Model {
821821
let resetText = input.provider == .sub2api && namedWindow.window.resetsAt == nil
822822
? nil
823823
: resolvedResetText
824-
let detailText = input.provider == .sub2api
825-
? namedWindow.window.resetDescription
826-
: nil
824+
let detailText: String? = if input.provider == .sub2api {
825+
namedWindow.window.resetDescription
826+
} else if input.provider == .zai, namedWindow.id == "zai-mcp" {
827+
Self.zaiLimitDetailText(limit: input.snapshot?.zaiUsage?.timeLimit)
828+
} else {
829+
nil
830+
}
827831
let statusText: String? = if usageKnown {
828832
nil
829833
} else if let resetText {
@@ -921,7 +925,9 @@ extension UsageMenuCardView.Model {
921925
window: RateWindow,
922926
input: Input) -> PaceDetail?
923927
{
924-
if provider == .claude, window.windowMinutes != 10080 { return nil }
928+
if provider == .claude, window.windowMinutes != 10080 {
929+
return nil
930+
}
925931
guard provider == .codex || provider == .claude || provider == .antigravity else { return nil }
926932
switch window.windowMinutes {
927933
case 300:

Sources/CodexBar/MenuCardView.swift

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,9 +1181,10 @@ extension UsageMenuCardView.Model {
11811181
var metrics: [Metric] = []
11821182
let percentStyle: PercentStyle = input.usageBarsShowUsed ? .used : .left
11831183
let zaiUsage = input.provider == .zai ? snapshot.zaiUsage : nil
1184-
let zaiTokenDetail = Self.zaiLimitDetailText(limit: zaiUsage?.tokenLimit)
1185-
let zaiTimeDetail = Self.zaiLimitDetailText(limit: zaiUsage?.timeLimit)
1186-
let zaiSessionDetail = Self.zaiLimitDetailText(limit: zaiUsage?.sessionTokenLimit)
1184+
let zaiPrimaryDetail = Self.zaiLimitDetailText(limit: zaiUsage?.sessionTokenLimit ?? zaiUsage?.tokenLimit)
1185+
let zaiSecondaryDetail = zaiUsage?.sessionTokenLimit == nil
1186+
? nil
1187+
: Self.zaiLimitDetailText(limit: zaiUsage?.tokenLimit)
11871188
let openRouterQuotaDetail = Self.openRouterQuotaDetail(
11881189
provider: input.provider,
11891190
snapshot: snapshot,
@@ -1214,7 +1215,7 @@ extension UsageMenuCardView.Model {
12141215
primary: primary,
12151216
percentStyle: percentStyle,
12161217
title: labels.primary,
1217-
zaiTokenDetail: zaiTokenDetail,
1218+
zaiTokenDetail: zaiPrimaryDetail,
12181219
openRouterQuotaDetail: openRouterQuotaDetail))
12191220
}
12201221
if input.provider != .codex, let weekly = snapshot.secondary {
@@ -1223,7 +1224,7 @@ extension UsageMenuCardView.Model {
12231224
weekly: weekly,
12241225
percentStyle: percentStyle,
12251226
title: labels.secondary,
1226-
zaiTimeDetail: zaiTimeDetail))
1227+
zaiTimeDetail: zaiSecondaryDetail))
12271228
}
12281229
if input.provider == .mimo, let mimoUsage = snapshot.mimoUsage {
12291230
metrics.append(Metric(
@@ -1247,9 +1248,6 @@ extension UsageMenuCardView.Model {
12471248
{
12481249
tertiaryDetailText = detail
12491250
}
1250-
if input.provider == .zai, let detail = zaiSessionDetail {
1251-
tertiaryDetailText = detail
1252-
}
12531251
// Perplexity purchased credits don't reset; show balance without "Resets" prefix.
12541252
let opusResetText: String? = input.provider == .perplexity || input.provider == .sub2api
12551253
? opus.resetDescription?.trimmingCharacters(in: .whitespacesAndNewlines)

Sources/CodexBar/MenuDescriptor.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,16 @@ struct MenuDescriptor {
323323
showUsed: settings.usageBarsShowUsed,
324324
resetOverride: opusResetOverride)
325325
}
326+
if provider == .zai {
327+
for extra in snap.extraRateWindows ?? [] where extra.id == "zai-mcp" {
328+
Self.appendRateWindow(
329+
entries: &entries,
330+
title: extra.title,
331+
window: extra.window,
332+
resetStyle: resetStyle,
333+
showUsed: settings.usageBarsShowUsed)
334+
}
335+
}
326336

327337
Self.appendProviderUsageSummaries(
328338
entries: &entries,

Sources/CodexBar/Providers/Kimi/KimiProviderImplementation.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,9 @@ struct KimiProviderImplementation: ProviderImplementation {
7575
ProviderSettingsPickerDescriptor(
7676
id: "kimi-usage-source",
7777
title: "Usage source",
78-
subtitle: "Auto tries your configured API key, then a signed-in Kimi Code CLI credential, " +
79-
"then browser cookies.",
78+
subtitle: "Kimi Code subscription usage from api.kimi.com. Auto tries your configured API key, " +
79+
"then a signed-in Kimi Code CLI credential, then web cookies. China Open Platform balance " +
80+
"is a separate provider.",
8081
binding: usageBinding,
8182
options: usageOptions,
8283
isVisible: nil,
@@ -103,8 +104,9 @@ struct KimiProviderImplementation: ProviderImplementation {
103104
[
104105
ProviderSettingsFieldDescriptor(
105106
id: "kimi-api-key",
106-
title: "API key",
107-
subtitle: "Stored in ~/.codexbar/config.json. You can also provide KIMI_CODE_API_KEY.",
107+
title: "Kimi Code API key",
108+
subtitle: "Kimi Code key from www.kimi.com/code. For China Open Platform balance, use " +
109+
"Moonshot / Kimi Open Platform.",
108110
kind: .secure,
109111
placeholder: "Paste Kimi Code API key...",
110112
binding: context.stringBinding(\.kimiAPIKey),

Sources/CodexBar/Providers/Moonshot/MoonshotProviderImplementation.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ struct MoonshotProviderImplementation: ProviderImplementation {
2626

2727
@MainActor
2828
func isAvailable(context: ProviderAvailabilityContext) -> Bool {
29-
if MoonshotSettingsReader.apiKey(environment: context.environment) != nil {
29+
let region = context.settings.moonshotRegion
30+
if MoonshotSettingsReader.apiKey(for: region, environment: context.environment) != nil {
3031
return true
3132
}
3233
context.settings.ensureMoonshotAPITokenLoaded()
33-
return !context.settings.moonshotAPIToken.trimmingCharacters(in: .whitespacesAndNewlines)
34-
.isEmpty
34+
return context.settings.hasMoonshotAPIToken(for: region)
3535
}
3636

3737
@MainActor
@@ -49,7 +49,8 @@ struct MoonshotProviderImplementation: ProviderImplementation {
4949
ProviderSettingsPickerDescriptor(
5050
id: "moonshot-api-region",
5151
title: "API region",
52-
subtitle: "Choose the Moonshot/Kimi API host for international or China mainland accounts.",
52+
subtitle: "Open-platform balance only. Keys are bound to the selected regional host and cannot be " +
53+
"sent to the other region.",
5354
binding: binding,
5455
options: options,
5556
isVisible: nil,
@@ -62,21 +63,20 @@ struct MoonshotProviderImplementation: ProviderImplementation {
6263
[
6364
ProviderSettingsFieldDescriptor(
6465
id: "moonshot-api-key",
65-
title: "API key",
66-
subtitle: "Stored in ~/.codexbar/config.json.",
66+
title: "Open Platform API key",
67+
subtitle: "Use a key issued for the selected region. Changing regions leaves the other key " +
68+
"unavailable until you switch back or replace it.",
6769
kind: .secure,
6870
placeholder: "sk-...",
6971
binding: context.stringBinding(\.moonshotAPIToken),
7072
actions: [
7173
ProviderSettingsActionDescriptor(
7274
id: "moonshot-open-dashboard",
73-
title: "Open Moonshot Console",
75+
title: "Open regional console",
7476
style: .link,
7577
isVisible: nil,
7678
perform: {
77-
if let url = URL(string: "https://platform.moonshot.ai/console/account") {
78-
NSWorkspace.shared.open(url)
79-
}
79+
NSWorkspace.shared.open(context.settings.moonshotRegion.consoleURL)
8080
}),
8181
],
8282
isVisible: nil,

Sources/CodexBar/Providers/Moonshot/MoonshotSettingsStore.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@ import Foundation
33

44
extension SettingsStore {
55
var moonshotAPIToken: String {
6-
get { self.configSnapshot.providerConfig(for: .moonshot)?.sanitizedAPIKey ?? "" }
6+
get {
7+
guard let config = self.configSnapshot.providerConfig(for: .moonshot),
8+
config.sanitizedAPIKeyRegion == self.moonshotRegion.rawValue
9+
else { return "" }
10+
return config.sanitizedAPIKey ?? ""
11+
}
712
set {
813
self.updateProviderConfig(provider: .moonshot) { entry in
914
entry.apiKey = self.normalizedConfigValue(newValue)
15+
entry.apiKeyRegion = entry.apiKey == nil ? nil : self.moonshotRegion.rawValue
1016
}
1117
self.logSecretUpdate(provider: .moonshot, field: "apiKey", value: newValue)
1218
}
@@ -26,6 +32,13 @@ extension SettingsStore {
2632

2733
func ensureMoonshotAPITokenLoaded() {}
2834

35+
func hasMoonshotAPIToken(for region: MoonshotRegion) -> Bool {
36+
guard let config = self.configSnapshot.providerConfig(for: .moonshot),
37+
config.sanitizedAPIKeyRegion == region.rawValue
38+
else { return false }
39+
return config.sanitizedAPIKey != nil
40+
}
41+
2942
var configuredMoonshotRegion: MoonshotRegion? {
3043
guard let raw = self.configSnapshot.providerConfig(for: .moonshot)?.region?
3144
.trimmingCharacters(in: .whitespacesAndNewlines),

Sources/CodexBar/SessionQuotaNotifications.swift

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -419,11 +419,6 @@ extension UsageStore {
419419
: .antigravityLegacy
420420
return (window, source)
421421
}
422-
// z.ai's typed sessionTokenLimit is rendered in the tertiary lane when the response also
423-
// contains its weekly token limit and MCP time limit. Prefer that semantic session lane.
424-
if provider == .zai, let tertiary = snapshot.tertiary {
425-
return (tertiary, .zaiTertiary)
426-
}
427422
if let primary = snapshot.primary, Self.isSessionWindow(primary) {
428423
// Crof credits-only balances publish a duration-less primary with no secondary quota
429424
// window. Keep that PAYG shape out of session-quota transitions so a $0 balance cannot

0 commit comments

Comments
 (0)