-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add WAL-safe Kimi Desktop monthly enrichment #2622
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| import Foundation | ||
|
|
||
| #if canImport(SQLite3) | ||
| import SQLite3 | ||
| #elseif canImport(CSQLite3) | ||
| import CSQLite3 | ||
| #endif | ||
|
|
||
| #if canImport(SQLite3) || canImport(CSQLite3) | ||
| /// Read-only access to the official Kimi Desktop Chromium cookie store. | ||
| public enum KimiDesktopAuthToken: Sendable { | ||
| private static let log = CodexBarLog.logger(LogCategories.kimiCookie) | ||
|
|
||
| public static func cookiesDatabaseURL( | ||
| homeDirectory: URL = FileManager.default.homeDirectoryForCurrentUser) -> URL | ||
| { | ||
| homeDirectory | ||
| .appendingPathComponent("Library", isDirectory: true) | ||
| .appendingPathComponent("Application Support", isDirectory: true) | ||
| .appendingPathComponent("kimi-desktop", isDirectory: true) | ||
| .appendingPathComponent("Cookies", isDirectory: false) | ||
| } | ||
|
|
||
| public static func load( | ||
| homeDirectory: URL = FileManager.default.homeDirectoryForCurrentUser) -> String? | ||
| { | ||
| self.load(databaseURL: self.cookiesDatabaseURL(homeDirectory: homeDirectory)) | ||
| } | ||
|
|
||
| static func load(databaseURL: URL) -> String? { | ||
| guard FileManager.default.isReadableFile(atPath: databaseURL.path) else { return nil } | ||
| do { | ||
| return try self.read(databaseURL: databaseURL, immutable: false) | ||
| } catch let failure as SQLiteReadFailure { | ||
| // Chromium can leave the main database in WAL mode after a clean shutdown removes both sidecars. | ||
| // Immutable mode reads that idle file without recreating sidecars; active WAL databases stay on the | ||
| // normal read-only path so committed WAL records remain visible. | ||
| guard failure.code == SQLITE_CANTOPEN, self.walSidecarsAreMissing(databaseURL: databaseURL) else { | ||
| Self.log.debug("Kimi Desktop Cookies read failed: \(failure.message)") | ||
| return nil | ||
| } | ||
| do { | ||
| return try self.read(databaseURL: databaseURL, immutable: true) | ||
| } catch let fallbackFailure as SQLiteReadFailure { | ||
| Self.log.debug("Kimi Desktop Cookies immutable read failed: \(fallbackFailure.message)") | ||
| return nil | ||
| } catch { | ||
| return nil | ||
| } | ||
| } catch { | ||
| return nil | ||
| } | ||
| } | ||
|
|
||
| private static func read(databaseURL: URL, immutable: Bool) throws -> String? { | ||
| var db: OpaquePointer? | ||
| let filename = immutable ? "\(databaseURL.absoluteURL.absoluteString)?immutable=1" : databaseURL.path | ||
| let flags = immutable ? SQLITE_OPEN_READONLY | SQLITE_OPEN_URI : SQLITE_OPEN_READONLY | ||
| let openResult = sqlite3_open_v2(filename, &db, flags, nil) | ||
| guard openResult == SQLITE_OK else { | ||
| let failure = self.sqliteFailure(db: db, resultCode: openResult) | ||
| sqlite3_close(db) | ||
| throw failure | ||
| } | ||
| defer { sqlite3_close(db) } | ||
| sqlite3_busy_timeout(db, 250) | ||
|
|
||
| let sql = """ | ||
| SELECT value | ||
| FROM cookies | ||
| WHERE name = 'kimi-auth' | ||
| AND host_key IN ('www.kimi.com', '.www.kimi.com', '.kimi.com', 'kimi.com') | ||
| ORDER BY last_access_utc DESC | ||
| LIMIT 1 | ||
| """ | ||
| var statement: OpaquePointer? | ||
| let prepareResult = sqlite3_prepare_v2(db, sql, -1, &statement, nil) | ||
| guard prepareResult == SQLITE_OK else { | ||
| throw self.sqliteFailure(db: db, resultCode: prepareResult) | ||
| } | ||
| defer { sqlite3_finalize(statement) } | ||
|
|
||
| let step = sqlite3_step(statement) | ||
| if step == SQLITE_DONE { | ||
| return nil | ||
| } | ||
| guard step == SQLITE_ROW else { | ||
| throw self.sqliteFailure(db: db, resultCode: step) | ||
| } | ||
| guard let text = sqlite3_column_text(statement, 0) else { return nil } | ||
| let token = String(cString: text).trimmingCharacters(in: .whitespacesAndNewlines) | ||
| return token.isEmpty ? nil : token | ||
| } | ||
|
|
||
| private static func walSidecarsAreMissing(databaseURL: URL) -> Bool { | ||
| !FileManager.default.fileExists(atPath: databaseURL.path + "-wal") && | ||
| !FileManager.default.fileExists(atPath: databaseURL.path + "-shm") | ||
| } | ||
|
|
||
| private static func sqliteFailure(db: OpaquePointer?, resultCode: Int32) -> SQLiteReadFailure { | ||
| SQLiteReadFailure( | ||
| code: db.map { sqlite3_errcode($0) } ?? resultCode, | ||
| message: db.map { String(cString: sqlite3_errmsg($0)) } ?? "unknown error") | ||
| } | ||
|
|
||
| private struct SQLiteReadFailure: Error { | ||
| let code: Int32 | ||
| let message: String | ||
| } | ||
| } | ||
| #else | ||
| public enum KimiDesktopAuthToken: Sendable { | ||
| public static func cookiesDatabaseURL( | ||
| homeDirectory: URL = FileManager.default.homeDirectoryForCurrentUser) -> URL | ||
| { | ||
| homeDirectory | ||
| .appendingPathComponent("Library", isDirectory: true) | ||
| .appendingPathComponent("Application Support", isDirectory: true) | ||
| .appendingPathComponent("kimi-desktop", isDirectory: true) | ||
| .appendingPathComponent("Cookies", isDirectory: false) | ||
| } | ||
|
|
||
| public static func load(homeDirectory _: URL = FileManager.default.homeDirectoryForCurrentUser) -> String? { | ||
| nil | ||
| } | ||
| } | ||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ public struct KimiUsageFetcher: Sendable { | |
| apiKey: String, | ||
| baseURL: URL = KimiSettingsReader.defaultCodeAPIBaseURL, | ||
| identityHeaders: [String: String] = [:], | ||
| webAuthToken: String? = nil, | ||
| now: Date = Date(), | ||
| transport: any ProviderHTTPTransport = ProviderHTTPClient.shared) async throws -> KimiUsageSnapshot | ||
| { | ||
|
|
@@ -44,7 +45,13 @@ public struct KimiUsageFetcher: Sendable { | |
| throw self.codeAPIError(statusCode: response.statusCode) | ||
| } | ||
|
|
||
| return try self.parseCodeAPIUsage(from: data, now: now) | ||
| let snapshot = try self.parseCodeAPIUsage(from: data, now: now) | ||
| guard let webAuthToken else { return snapshot } | ||
| return try await self.enrichCodeAPIUsage( | ||
| snapshot, | ||
| webAuthToken: webAuthToken, | ||
| now: now, | ||
| transport: transport) | ||
|
Comment on lines
+50
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a web session is available but Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| static func _parseCodeAPIUsageForTesting(_ data: Data, now: Date = Date()) throws -> KimiUsageSnapshot { | ||
|
|
@@ -179,6 +186,35 @@ public struct KimiUsageFetcher: Sendable { | |
| return codingUsage | ||
| } | ||
|
|
||
| private static func enrichCodeAPIUsage( | ||
| _ snapshot: KimiUsageSnapshot, | ||
| webAuthToken: String, | ||
| now: Date, | ||
| transport: any ProviderHTTPTransport) async throws -> KimiUsageSnapshot | ||
| { | ||
| let sessionInfo = self.decodeSessionInfo(from: webAuthToken) | ||
| let subscriptionStats: KimiSubscriptionStatsResponse? | ||
| do { | ||
| subscriptionStats = try await self.fetchSubscriptionStats( | ||
| authToken: webAuthToken, | ||
| sessionInfo: sessionInfo, | ||
| transport: transport) | ||
| } catch is CancellationError { | ||
| throw CancellationError() | ||
| } catch { | ||
| Self.log.warning("Kimi Code monthly enrichment unavailable: \(error.localizedDescription)") | ||
| return snapshot | ||
| } | ||
| guard let subscriptionStats else { return snapshot } | ||
| return KimiUsageSnapshot( | ||
| weekly: snapshot.weekly, | ||
| rateLimit: snapshot.rateLimit, | ||
| rateLimitWindow: snapshot.rateLimitWindow, | ||
| subscriptionBalance: subscriptionStats.subscriptionBalance, | ||
| subscriptionCodeWeeklyLimit: subscriptionStats.ratelimitCode7d, | ||
| updatedAt: now) | ||
| } | ||
|
|
||
| private static func parseCodeAPIUsage(from data: Data, now: Date) throws -> KimiUsageSnapshot { | ||
| let response = try JSONDecoder().decode(KimiCodeAPIUsageResponse.self, from: data) | ||
| let rateLimit = response.limits?.first | ||
|
|
||
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.
When the user disables optional credits/extra usage,
ProviderRegistrystill passescontext.includeOptionalUsage == false, but this resolver only checks Cookie Source. In API/CLI Kimi modes that means CodexBar still resolves desktop/browser cookies, sends the monthly membership request, and rendersextraRateWindowsthat the global opt-out is supposed to suppress; short-circuit Kimi web enrichment whenincludeOptionalUsageis false.Useful? React with 👍 / 👎.