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
4 changes: 4 additions & 0 deletions Sources/CodexBar/MenuCardView+ModelHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -288,11 +288,15 @@ extension UsageMenuCardView.Model {
Sub2APIProviderDescriptor.primaryLabel(details: snapshot.sub2APIUsage) ?? input.metadata.sessionLabel
} else if input.provider == .amp {
AmpProviderDescriptor.primaryLabel(details: snapshot.ampUsage) ?? input.metadata.sessionLabel
} else if input.provider == .alibabatokenplan {
AlibabaTokenPlanProviderDescriptor.primaryLabel(window: snapshot.primary) ?? input.metadata.sessionLabel
} else {
input.metadata.sessionLabel
}
let secondaryLabel = if input.provider == .amp {
AmpProviderDescriptor.secondaryLabel(details: snapshot.ampUsage) ?? input.metadata.weeklyLabel
} else if input.provider == .alibabatokenplan {
AlibabaTokenPlanProviderDescriptor.secondaryLabel(window: snapshot.secondary) ?? input.metadata.weeklyLabel
} else {
input.metadata.weeklyLabel
}
Expand Down
4 changes: 4 additions & 0 deletions Sources/CodexBar/MenuDescriptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -678,11 +678,15 @@ struct MenuDescriptor {
Sub2APIProviderDescriptor.primaryLabel(details: snapshot.sub2APIUsage) ?? metadata.sessionLabel
} else if provider == .amp {
AmpProviderDescriptor.primaryLabel(details: snapshot.ampUsage) ?? metadata.sessionLabel
} else if provider == .alibabatokenplan {
AlibabaTokenPlanProviderDescriptor.primaryLabel(window: snapshot.primary) ?? metadata.sessionLabel
} else {
metadata.sessionLabel
}
let secondaryLabel = if provider == .amp {
AmpProviderDescriptor.secondaryLabel(details: snapshot.ampUsage) ?? metadata.weeklyLabel
} else if provider == .alibabatokenplan {
AlibabaTokenPlanProviderDescriptor.secondaryLabel(window: snapshot.secondary) ?? metadata.weeklyLabel
} else {
metadata.weeklyLabel
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,15 @@ struct AlibabaTokenPlanProviderImplementation: ProviderImplementation {
allowsOff: false,
keychainDisabled: context.settings.debugDisableKeychainAccess)
let cookieSubtitle: () -> String? = {
let host = context.settings.alibabaTokenPlanAPIRegion.dashboardURL.host ?? "the selected console"
let region = context.settings.alibabaTokenPlanAPIRegion
let host = region.usesPersonalTokenPlanAPI
? URL(string: region.quotaBaseURLString)?.host
: region.dashboardURL.host
return ProviderCookieSourceUI.subtitle(
source: context.settings.alibabaTokenPlanCookieSource,
keychainDisabled: context.settings.debugDisableKeychainAccess,
auto: "Automatic imports browser cookies from Model Studio/Bailian.",
manual: "Paste a Cookie header from \(host).",
manual: "Paste a Cookie header from \(host ?? "the selected console").",
off: "Alibaba Token Plan cookies are disabled.")
}

Expand Down
8 changes: 8 additions & 0 deletions Sources/CodexBar/UsageStore+WidgetSnapshot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,18 @@ extension UsageStore {
{
return dyn
}
if provider == .alibabatokenplan,
let dyn = AlibabaTokenPlanProviderDescriptor.primaryLabel(window: snapshot.primary)
{
return dyn
}
return metadata?.sessionLabel ?? "Session"
}()
let secondaryTitle = if provider == .amp {
AmpProviderDescriptor.secondaryLabel(details: snapshot.ampUsage) ?? metadata?.weeklyLabel ?? "Weekly"
} else if provider == .alibabatokenplan {
AlibabaTokenPlanProviderDescriptor.secondaryLabel(window: snapshot.secondary) ??
metadata?.weeklyLabel ?? "Weekly"
} else {
metadata?.weeklyLabel ?? "Weekly"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,44 @@ import Foundation
public enum AlibabaTokenPlanAPIRegion: String, CaseIterable, Sendable {
case international = "intl"
case chinaMainland = "cn"
case internationalPersonal = "intl-personal"
case chinaMainlandPersonal = "cn-personal"

public var displayName: String {
switch self {
case .international:
"International (modelstudio.console.alibabacloud.com)"
"International — Team (modelstudio.console.alibabacloud.com)"
case .chinaMainland:
"China mainland (bailian.console.aliyun.com)"
"China mainland — Team (bailian.console.aliyun.com)"
case .internationalPersonal:
"International — Personal/Solo (modelstudio.console.alibabacloud.com)"
case .chinaMainlandPersonal:
"China mainland — Personal/Solo (bailian.console.aliyun.com)"
}
}

public var gatewayBaseURLString: String {
switch self {
case .international:
case .international, .internationalPersonal:
"https://modelstudio.console.alibabacloud.com"
case .chinaMainland:
case .chinaMainland, .chinaMainlandPersonal:
"https://bailian.console.aliyun.com"
}
}

public var quotaBaseURLString: String {
switch self {
case .international:
self.gatewayBaseURLString
case .chinaMainland:
self.gatewayBaseURLString
case .internationalPersonal:
"https://bailian-singapore-cs.alibabacloud.com"
case .chinaMainlandPersonal:
"https://bailian-cs.console.aliyun.com"
}
}

public var dashboardOriginURLString: String {
self.gatewayBaseURLString
}
Expand All @@ -33,14 +52,22 @@ public enum AlibabaTokenPlanAPIRegion: String, CaseIterable, Sendable {
string: "https://modelstudio.console.alibabacloud.com/ap-southeast-1/?tab=plan#/efm/subscription/token-plan")!
case .chinaMainland:
URL(string: "https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/token-plan")!
case .internationalPersonal:
URL(
string: "https://modelstudio.console.alibabacloud.com/ap-southeast-1/" +
"?tab=plan#/efm/subscription/token-plan/personal")!
case .chinaMainlandPersonal:
URL(
string: "https://bailian.console.aliyun.com/cn-beijing" +
"?tab=plan#/efm/subscription/token-plan/personal")!
}
}

public var currentRegionID: String {
switch self {
case .international:
case .international, .internationalPersonal:
"ap-southeast-1"
case .chinaMainland:
case .chinaMainland, .chinaMainlandPersonal:
"cn-beijing"
}
}
Expand All @@ -51,6 +78,38 @@ public enum AlibabaTokenPlanAPIRegion: String, CaseIterable, Sendable {
"sfm_tokenplanteams_dp_intl"
case .chinaMainland:
"sfm_tokenplanteams_dp_cn"
case .internationalPersonal:
"sfm_tokenplansolo_public_intl"
case .chinaMainlandPersonal:
"sfm_tokenplansolo_public_cn"
}
}

public var usesPersonalTokenPlanAPI: Bool {
switch self {
case .international, .chinaMainland:
false
case .internationalPersonal, .chinaMainlandPersonal:
true
}
}

public var personalAPIAction: String {
switch self {
case .international, .internationalPersonal:
"IntlBroadScopeAspnGateway"
case .chinaMainland, .chinaMainlandPersonal:
"BroadScopeAspnGateway"
}
}

public var personalConsoleSite: String {
switch self {
case .international, .internationalPersonal:
// This is Alibaba's live console contract, including its historical spelling.
"MODELSTUDIO_ALBABACLOUD"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the Model Studio consoleSite spelling

When a user selects the International — Personal/Solo variant, the Personal request body sends this consoleSite value through IntlBroadScopeAspnGateway, but the existing Model Studio OneConsole path in AlibabaCodingPlanAPIRegion.consoleSite uses MODELSTUDIO_ALIBABACLOUD. The new tests only assert the mainland Personal request body, so this typo can leave the international Personal gateway receiving an unrecognized site id while the mainland variant still works.

Useful? React with 👍 / 👎.

case .chinaMainland, .chinaMainlandPersonal:
"BAILIAN_ALIYUN"
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import Foundation

/// Shared parser for Alibaba Token Plan Personal/Solo rolling-window responses.
/// Both mainland and international variants expose the same payload contract.
enum AlibabaTokenPlanPersonalUsageParser {
static func parse(
from usageData: Data,
subscriptionData: Data?,
quotaConfigData: Data?,
now: Date) throws -> AlibabaTokenPlanUsageSnapshot
{
guard !usageData.isEmpty else {
throw AlibabaTokenPlanUsageError.parseFailed("Empty response body")
}

let raw: Any
do {
raw = try JSONSerialization.jsonObject(with: usageData)
} catch {
if let text = String(data: usageData, encoding: .utf8)?.lowercased(),
text.contains("<html"),
text.contains("login") || text.contains("sign in") || text.contains("signin")
{
throw AlibabaTokenPlanUsageError.loginRequired
}
throw AlibabaTokenPlanUsageError.parseFailed("Invalid JSON response")
}

let expanded = OneConsoleJSON.expandEmbeddedJSON(raw)
guard let dictionary = expanded as? [String: Any] else {
throw AlibabaTokenPlanUsageError.parseFailed("Unexpected payload")
}
try AlibabaTokenPlanUsageFetcher.throwIfErrorPayload(dictionary)
guard let usage = OneConsoleJSON.findObject(
containingAnyOf: ["per5HourPercentage", "per1WeekPercentage"],
in: expanded)
else {
throw AlibabaTokenPlanUsageError.parseFailed("Missing Personal usage windows")
}

let fiveHourPercent = OneConsoleJSON.percentagePoints(
fromRatio: OneConsoleJSON.number(usage["per5HourPercentage"]))
let weeklyPercent = OneConsoleJSON.percentagePoints(
fromRatio: OneConsoleJSON.number(usage["per1WeekPercentage"]))
guard fiveHourPercent != nil || weeklyPercent != nil else {
throw AlibabaTokenPlanUsageError.parseFailed("Missing Personal usage windows")
}

let planCode = subscriptionData.flatMap(self.planCode)
let quota = quotaConfigData.flatMap {
self.quotaTotals(from: $0, planCode: planCode)
}
return AlibabaTokenPlanUsageSnapshot(
planName: planCode.map(self.displayPlanName) ?? "Personal",
usedQuota: nil,
totalQuota: nil,
remainingQuota: nil,
resetsAt: nil,
fiveHourUsedPercent: fiveHourPercent,
fiveHourTotalQuota: quota?.fiveHour,
fiveHourResetsAt: OneConsoleJSON.date(usage["per5HourResetTime"]),
weeklyUsedPercent: weeklyPercent,
weeklyTotalQuota: quota?.weekly,
weeklyResetsAt: OneConsoleJSON.date(usage["per1WeekResetTime"]),
updatedAt: now)
}

private static func planCode(from data: Data) -> String? {
guard let raw = try? JSONSerialization.jsonObject(with: data) else { return nil }
let expanded = OneConsoleJSON.expandEmbeddedJSON(raw)
guard let plan = OneConsoleJSON.findObject(
containingAnyOf: ["specCode", "spec_code", "planName", "plan_name"],
in: expanded)
else {
return nil
}
for key in ["specCode", "spec_code", "planName", "plan_name"] {
if let value = OneConsoleJSON.string(plan[key])?.lowercased(), !value.isEmpty {
return value
}
}
return nil
}

private static func displayPlanName(_ planCode: String) -> String {
switch planCode {
case "lite": "Lite"
case "standard": "Standard"
case "pro": "Pro"
case "max": "Max"
default: planCode
}
}

private static func quotaTotals(
from data: Data,
planCode: String?) -> (fiveHour: Double?, weekly: Double?)?
{
guard let planCode,
let raw = try? JSONSerialization.jsonObject(with: data)
else {
return nil
}
let expanded = OneConsoleJSON.expandEmbeddedJSON(raw)
guard let value = OneConsoleJSON.findFirstValue(forKeys: [planCode], in: expanded),
let quota = value as? [String: Any]
else {
return nil
}
let fiveHour = OneConsoleJSON.number(quota["five_hour"] ?? quota["fiveHour"])
let weekly = OneConsoleJSON.number(quota["weekly"])
guard fiveHour != nil || weekly != nil else { return nil }
return (fiveHour, weekly)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ import SweetCookieKit
public enum AlibabaTokenPlanProviderDescriptor {
public static let descriptor: ProviderDescriptor = Self.makeDescriptor()

public static func primaryLabel(window: RateWindow?) -> String? {
window?.windowMinutes == 5 * 60 ? "5-hour" : nil
}

public static func secondaryLabel(window: RateWindow?) -> String? {
window?.windowMinutes == 7 * 24 * 60 ? "7-day" : nil
}

static func makeDescriptor() -> ProviderDescriptor {
#if os(macOS)
let browserOrder: BrowserCookieImportOrder = [
Expand Down
Loading