Skip to content

Add Alibaba Cloud Model Studio provider#559

Closed
GLunPy wants to merge 10 commits intosteipete:mainfrom
GLunPy:feature/alibaba-provider
Closed

Add Alibaba Cloud Model Studio provider#559
GLunPy wants to merge 10 commits intosteipete:mainfrom
GLunPy:feature/alibaba-provider

Conversation

@GLunPy
Copy link
Copy Markdown

@GLunPy GLunPy commented Mar 17, 2026

Features:

  • WebView-based usage monitoring for Coding Plan
  • Supports Lite and Pro plans
  • 5h, 7d, and 30d usage windows
  • Orange branding (#FF6A00)

Testing:

  • DOM selectors verified with real account
  • Requires macOS for compilation

Features:
- WebView-based usage monitoring for Coding Plan
- Supports Lite and Pro plans
- 5h, 7d, and 30d usage windows
- Orange branding (#FF6A00)

Testing:
- DOM selectors verified with real account
- Requires macOS for compilation
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 81ab581ccf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

case synthetic
case warp
case openrouter
case alibaba
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P0 Badge Add Alibaba descriptor to bootstrap map

Adding .alibaba to UsageProvider here requires a matching entry in ProviderDescriptorRegistry.descriptorsByID (Sources/CodexBarCore/Providers/ProviderDescriptor.swift), but this commit does not add one. Because bootstrap iterates UsageProvider.allCases and calls preconditionFailure when a descriptor is missing, any code path that touches ProviderDescriptorRegistry.metadata/descriptor(for:) will crash with Missing ProviderDescriptor for alibaba (including normal startup flows).

Useful? React with 👍 / 👎.

Fix: Register AlibabaProviderDescriptor in descriptorsByID dict
to match the UsageProvider.alibaba case added in Providers.swift
Copy link
Copy Markdown
Author

@GLunPy GLunPy left a comment

Choose a reason for hiding this comment

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

"@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5679122b90

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +28 to +31
UsageSnapshot(
primary: sessionUsage,
secondary: weeklyUsage,
updatedAt: updatedAt,
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 Include monthly window when building usage snapshot

AlibabaUsageSnapshot captures monthlyUsage, but toUsageSnapshot() only forwards primary and secondary. This drops the 30-day metric before it reaches the shared UsageSnapshot model, so Alibaba users cannot see monthly usage despite the fetcher collecting it.

Useful? React with 👍 / 👎.

Comment on lines +129 to +130
dateFormatter.timeZone = TimeZone(identifier: "UTC")
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
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 Parse Alibaba reset timestamps in console timezone

The reset strings parsed here (yyyy-MM-dd HH:mm:ss Reset) carry no timezone information, but the formatter is forced to UTC. For accounts where the console displays local (non-UTC) times, this shifts resetsAt by the timezone offset and produces incorrect reset countdowns and pacing calculations.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@codex review

1. Add note explaining monthlyUsage limitation:
   - UsageSnapshot only supports primary + secondary windows
   - monthlyUsage (30d) is collected but not forwarded
   - Document this in toUsageSnapshot() comment

2. Fix timezone parsing for reset timestamps:
   - Use Asia/Singapore timezone (ap-southeast-1 region)
   - Console displays local times, not UTC
   - This ensures correct reset countdown calculations
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bf63255000

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +70 to +72
guard let result = try await webView.evaluateJavaScript(script) as? [String: Any?],
let jsonData = try? JSONSerialization.data(withJSONObject: result),
let response = try? JSONDecoder().decode(AlibabaConsoleResponse.self, from: jsonData)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Fail when DOM selectors return only null usage fields

This decode step treats an object full of null values as a successful parse because every field in AlibabaConsoleResponse is optional, and parseUsageResponse then defaults those missing values to 0%/synthetic reset dates. When selectors drift or the page hasn’t rendered yet, users will see a seemingly valid low-usage snapshot instead of an error, which can hide scraping breakage and mislead quota decisions. Validate that at least one required usage field is present before returning a snapshot.

Useful? React with 👍 / 👎.

dateFormatter.locale = Locale(identifier: "en_US_POSIX")
// Use Singapore timezone for ap-southeast-1 region
// Users in other regions will see their local console times
dateFormatter.timeZone = TimeZone(identifier: "Asia/Singapore")
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 Parse reset timestamps in the displayed timezone

The reset text has no timezone offset, but this parser always forces Asia/Singapore, so any account/browser showing times in another timezone gets shifted reset dates and incorrect countdowns by multiple hours. The comments here already note users may see local console times, so hardcoding this zone will produce wrong results in those environments; parse in the displayed/local timezone (or derive it explicitly) instead.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@codex review

P1 (Critical): Add validation for required fields
- Validate at least one usage field is non-null before returning
- Prevents returning 0% snapshot when selectors drift
- Throws parseFailed error with descriptive message

P2 (Timezone): Remove hardcoded Asia/Singapore timezone
- Parse reset timestamps using system timezone
- Console displays times in user's local region
- Hardcoding SGT broke countdowns for users in other regions
- Now respects whatever timezone the user's console shows
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7bc59aa4e0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +89 to +91
let usage5hPercent = parsePercentage(response.usage5h) ?? 0.0
let usage7dPercent = parsePercentage(response.usage7d) ?? 0.0
let usage30dPercent = parsePercentage(response.usage30d) ?? 0.0
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 Treat unparsable usage fields as fetch errors

This parser silently converts any missing or malformed percentage string into 0.0, so if one selector drifts or the page renders partial data, the app reports a real-looking 0% value instead of failing the fetch. In that scenario users are shown artificially low usage for that window and can unknowingly exceed quota; required usage fields should be validated as parseable before building a snapshot.

Useful? React with 👍 / 👎.

Comment on lines +142 to +143
let components = string.components(separatedBy: " Reset")
if let dateString = components.first {
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 Parse reset dates without assuming English "Reset" suffix

Reset parsing currently depends on splitting the text with the literal " Reset"; when the console language changes or the suffix wording differs, parsing falls back to Date() + window, which makes the displayed reset countdown inaccurate on every refresh. This is user-visible for non-English or changed UI text and should be handled by parsing the timestamp independently of localized trailing labels.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@codex review

1. parsePercentage now validates input:
   - Returns nil for empty strings (not silently 0.0)
   - Returns nil if string is not a valid number
   - Prevents showing fake 0% when selectors drift

2. parseResetTime no longer assumes English 'Reset' suffix:
   - Extract first 19 chars (timestamp: yyyy-MM-dd HH:mm:ss)
   - Ignore any trailing localized text ('Reset', 'Reinicia', etc.)
   - Works regardless of console language setting
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0092373e00

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +79 to +80
guard response.usage5h != nil || response.usage7d != nil || response.usage30d != nil else {
throw AlibabaUsageError.parseFailed("No usage fields found - selectors may have drifted or page not rendered")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Fail when any expected usage window cannot be parsed

This validation only requires one of the three usage fields to exist, so partial selector drift still produces a “successful” snapshot and parseUsageResponse fills missing/unparseable windows with 0.0. In practice, if the 5h or 7d selector breaks while 30d still resolves, users can be shown near-zero usage for the broken windows instead of getting a parse error, which is a misleading quota signal.

Useful? React with 👍 / 👎.

Comment on lines +30 to +33
UsageSnapshot(
primary: sessionUsage,
secondary: weeklyUsage,
updatedAt: updatedAt,
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 Forward the collected 30-day usage window

The fetcher and snapshot model collect monthlyUsage, but this conversion drops it and only emits primary/secondary windows, so downstream UI/state never receives the 30-day data. As written, the provider advertises 30d support in code comments/features but discards that window before returning the shared snapshot.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@codex review

P1 (Critical): Require ALL three usage fields to be present
- Changed validation from OR (any 1 of 3) to AND (all 3 required)
- If any window selector breaks, fail fast with descriptive error
- Lists exactly which fields are missing (5h, 7d, 30d)
- Prevents misleading 0.0% quota signals from partial selector drift

P2: Forward monthlyUsage as tertiary window
- UsageSnapshot supports primary, secondary, AND tertiary RateWindow fields
- Changed AlibabaUsageSnapshot to use RateWindow (standard type) instead of custom UsageWindow
- toUsageSnapshot() now forwards all three windows:
  - primary: sessionUsage (5h)
  - secondary: weeklyUsage (7d)
  - tertiary: monthlyUsage (30d) ← NOW FORWARDED
- Downstream UI will now receive complete 5h/7d/30d quota data
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0ee025245d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +97 to +99
let usage5hPercent = parsePercentage(response.usage5h) ?? 0.0
let usage7dPercent = parsePercentage(response.usage7d) ?? 0.0
let usage30dPercent = parsePercentage(response.usage30d) ?? 0.0
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Fail on non-numeric usage values instead of defaulting to 0%

This path silently converts parse failures into 0.0 usage for all windows, so if Alibaba changes a selector and the extracted text becomes non-numeric (for example, a label string instead of "46%"), the fetch still succeeds and reports a misleading “empty usage” state rather than surfacing a scrape failure. Because the guard above only checks for nil fields, malformed-but-present fields are treated as valid and can hide real quota exhaustion.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@codex review

CRITICAL FIX:
- parseUsageResponse now throws parseFailed error if any percentage parsing fails
- Changed from '?? 0.0' fallback to 'guard let ... else { throw }' pattern
- Each error message includes the actual value that failed to parse
- This ensures selector drift or format changes surface as errors, not fake 0% usage

Before: '46%' → 46.0, 'abc' → 0.0 (silent failure)
After:  '46%' → 46.0, 'abc' → throws error with message

This prevents misleading 'empty quota' signals when page format changes.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7e1771a7af

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let consoleURL = "https://modelstudio.console.alibabacloud.com/ap-southeast-1/?tab=globalset#/efm/coding_plan"

// Navigate to console with cookies
try await webView.load(url: consoleURL, cookies: cookies, timeout: 30)
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 Honor configured web timeout for Alibaba page load

fetchUsage hardcodes timeout: 30 when loading the dashboard, so this provider ignores ProviderFetchContext.webTimeout used by other web strategies. In slow networks or first-load SPA hydration cases where users raise the global web timeout, Alibaba fetches can still fail early and report parse/missing-field errors despite the configured timeout.

Useful? React with 👍 / 👎.

try await webView.load(url: consoleURL, cookies: cookies, timeout: 30)

// Wait for page to fully load
try await Task.sleep(nanoseconds: 3_000_000_000) // 3 seconds
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 Wait for usage selectors instead of sleeping a fixed 3s

The scraper waits a fixed 3 seconds and then reads all required selectors once, but the usage cards are populated asynchronously after navigation. When rendering or API calls take longer than 3 seconds, the required fields are still nil and the fetch fails with Missing usage fields, making usage collection flaky under normal slow-network conditions.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@codex review

1. Honor configured web timeout:
   - Changed fetchUsage signature to accept webTimeout parameter
   - Pass context.webTimeout instead of hardcoding timeout: 30
   - Respects user's global web timeout configuration
   - Prevents early failures on slow networks when user raised timeout

2. Wait for usage selectors instead of fixed 3s sleep:
   - Removed fixed 3 second Task.sleep
   - Added polling loop: check for [ref=e168], [ref=e187], [ref=e206]
   - Poll up to 20 attempts × 500ms = 10 seconds max
   - Breaks early when all 3 selectors are found
   - Throws pageLoadFailed if selectors don't appear in 10s
   - Handles slow networks and async SPA hydration properly

Before: Fixed 3s wait → flaky failures on slow renders
After:  Smart polling up to 10s → robust across network conditions
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: da038a1826

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +53 to +55
let script = "document.querySelector('[ref=\"\(selector)\"]')"
if let element = try await webView.evaluateJavaScript(script) as? [String: Any],
element["tagName"] != nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Return a primitive when polling selector readiness

The readiness loop evaluates document.querySelector(...) directly and then casts the result to a dictionary, but that expression returns a DOM Element object. In the WKWebView-backed path this result type is not bridgeable (only JSON-like primitives/collections are), so evaluateJavaScript can throw before parsing ever runs, causing Alibaba fetches to fail consistently in production. Use a primitive expression such as document.querySelector(...) !== null (or read a primitive property) for the polling check.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@codex review

The readiness loop was evaluating document.querySelector(...) directly
and casting to [String: Any], but WKWebView cannot bridge DOM Element
objects to Swift dictionaries - only JSON-like primitives work.

Changed to:
- Evaluate 'element !== null' which returns a primitive Bool
- Check 'if let exists = ... as? Bool, exists'
- No DOM object bridging, no evaluateJavaScript exceptions

This fixes consistent production failures in the Alibaba fetcher
where the polling loop threw before parsing could run.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c6cf30ece9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

dashboardURL: "https://modelstudio.console.alibabacloud.com/ap-southeast-1/?tab=globalset#/efm/coding_plan",
statusPageURL: nil),
branding: ProviderBranding(
iconStyle: .custom,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P0 Badge Use a declared IconStyle for Alibaba branding

ProviderBranding.iconStyle is typed as IconStyle, but IconStyle in Sources/CodexBarCore/Providers/Providers.swift has no .custom case. Leaving .custom here makes the new provider descriptor fail to type-check, which blocks compilation for any target that includes CodexBarCore.

Useful? React with 👍 / 👎.


func isAvailable(_ context: ProviderFetchContext) async -> Bool {
// Check if user has cookies for alibabacloud.com
return context.cookieImporter.hasCookies(for: "alibabacloud.com")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P0 Badge Make AlibabaWebFetchStrategy conform to fetch interfaces

The strategy wiring here does not match the current fetch pipeline contracts: ProviderFetchContext does not expose cookieImporter/webViewAPI, and this block also relies on unsupported strategy typing in the same struct. As a result, the Alibaba provider cannot compile or run through ProviderFetchPlan in this commit.

Useful? React with 👍 / 👎.

…porter

ProviderFetchContext does not expose cookieImporter or webViewAPI.
Following the CodexWebDashboardStrategy pattern instead:

- Use context.browserDetection for cookie access
- Use MainActor for WebView operations (AppKit init required)
- Check cookies via browserDetection.hasCookies(for:)
- Create WebView via browserDetection.createWebView()
- Add proper fallback logic for auto mode

This matches how OpenAI/Claude web scraping works in CodexBar.
@ratulsarna
Copy link
Copy Markdown
Collaborator

Closing as duplicate of #453

@ratulsarna ratulsarna closed this Mar 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants