Skip to content

feat: add Trae SOLO (ByteDance AI IDE) support - #344

Open
x7peeps wants to merge 1 commit into
xiufengsun:mainfrom
x7peeps:feat/trae-solo-support
Open

feat: add Trae SOLO (ByteDance AI IDE) support#344
x7peeps wants to merge 1 commit into
xiufengsun:mainfrom
x7peeps:feat/trae-solo-support

Conversation

@x7peeps

@x7peeps x7peeps commented Jul 20, 2026

Copy link
Copy Markdown

Summary

Add provider detection for Trae SOLO, ByteDance VS Code-based AI IDE.

Changes

  • resolveTraePath() - cross-platform path resolver
  • resolveTraeStoragePath() - locates storage.json
  • parseTraeIncremental() - reads entitlement data from Local State

Data captured

  • Plan identity (Free/Pro/Pro+/Enterprise)
  • Dollar billing status
  • Solo Builder/Coder feature flags
  • Fast request limits and waitlist status

Tested with Trae SOLO v2.3.55113 on macOS Apple Silicon.

Summary by CodeRabbit

  • New Features

    • Added support for displaying Trae SOLO entitlement and plan information from local account data.
    • Trae entitlement snapshots are recorded with current-hour usage metadata and zero token totals.
  • Bug Fixes

    • Standardized Pi usage tracking under a consistent source while preserving model-specific reporting.

Add provider detection for Trae SOLO, ByteDance's VS Code-based AI IDE.

- resolveTraePath(): cross-platform path resolver
- resolveTraeStoragePath(): locates storage.json
- parseTraeIncremental(): reads entitlement data from Local State
  (iCubeServerData://icube.cloudide)

Supported plans: Free, Pro, Pro+, Enterprise
Data: identity, solo_builder/coder flags, fast_request_per, waitlist
@x7peeps
x7peeps requested a review from xiufengsun as a code owner July 20, 2026 14:28
@github-actions github-actions Bot added the cli label Jul 20, 2026
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

parsePiIncremental now buckets usage under the fixed pi source. New Trae SOLO helpers locate storage.json, parse entitlement data, and append zero-token hourly queue entries.

Changes

Rollout parser updates

Layer / File(s) Summary
Fixed Pi source bucketing
src/lib/rollout.js
Pi parsing removes provider-derived source mapping and uses pi with the parsed or fallback model.
Trae entitlement snapshot parsing
src/lib/rollout.js
New path resolvers and parseTraeIncremental read Trae entitlement data and append synthetic hourly trae_entitlement queue entries with zero token totals.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant StorageJson
  participant parseTraeIncremental
  participant Queue
  StorageJson->>parseTraeIncremental: Read iCubeServerData entitlement data
  parseTraeIncremental->>Queue: Append synthetic hourly trae_entitlement entry
Loading

Suggested reviewers: mm7894215, encodets, mm7894215, xminhx, yanzzp999

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding Trae SOLO support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread src/lib/rollout.js
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
}

const raw = await fs.readFile(targetPath, "utf8");

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/rollout.js`:
- Around line 13275-13296: Update the entitlement processing around
entitlementInfo so parsed serverData without a valid entitlementInfo returns
early, persists cursorState.lastMtime and cursorState.updatedAt, and reports
zero processing counts like the missing serverData path. Only continue to derive
detail and the model when entitlementInfo is present, avoiding synthetic
trae-unknown entries.
- Around line 13252-13329: Update parseTraeIncremental so its cursor mutations
are persisted to the caller by assigning the updated cursorState back to
cursors.trae, following the existing parsePiIncremental pattern. Ensure this
assignment occurs before returning after the cursor is updated, including the
no-server-data path, while preserving the current mtime comparison and queue
behavior.
- Around line 13252-13257: Update parseTraeIncremental to use the supplied
traHome when resolving the fallback target path, rather than always calling
resolveTraeStoragePath with process.env. Preserve storagePath precedence and the
existing early return when the resolved target is missing.
- Around line 13213-13218: Register parseTraeIncremental in the rollout provider
invocation/canonical list alongside the existing Trae symbols so the rollout
dispatcher invokes it and collects Trae entitlement snapshots; keep the existing
export unchanged.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d115aa5a-d2ce-482b-b813-529ee9c3f28f

📥 Commits

Reviewing files that changed from the base of the PR and between 01c48bc and eb463dd.

📒 Files selected for processing (1)
  • src/lib/rollout.js

Comment thread src/lib/rollout.js
Comment on lines +13213 to 13218

// Trae SOLO (ByteDance AI IDE)
resolveTraePath,
resolveTraeStoragePath,
parseTraeIncremental,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "parseTraeIncremental|parsePiIncremental\b" src/lib/rollout.js -B3 -A3
rg -n "resolveTraePath|resolveTraeStoragePath" src -g '!**/rollout.js'

Repository: mm7894215/TokenTracker

Length of output: 1216


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the provider/canonical list and Trae parser definition in rollout.js
sed -n '13080,13320p' src/lib/rollout.js | cat -n

Repository: mm7894215/TokenTracker

Length of output: 9399


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find all Trae-related references in rollout.js and nearby rollout wiring.
rg -n "Trae|parse[A-Za-z]+Incremental|resolve[A-Za-z]+Path|resolve[A-Za-z]+StoragePath" src/lib/rollout.js

Repository: mm7894215/TokenTracker

Length of output: 8077


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the provider list / dispatcher in rollout.js and any Trae registration.
rg -n "canonical|providers|switch \\(|case .*Trae|Trae SOLO|parseTraeIncremental" src/lib/rollout.js -A2 -B2

Repository: mm7894215/TokenTracker

Length of output: 8374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search the repo for any actual Trae parser wiring/call sites.
rg -n "\bparseTraeIncremental\b|\bresolveTraePath\b|\bresolveTraeStoragePath\b" .

Repository: mm7894215/TokenTracker

Length of output: 758


Wire Trae into the rollout dispatcher

parseTraeIncremental is exported here, but it isn’t registered in the provider invocation/canonical list, so Trae entitlement snapshots won’t be collected.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/rollout.js` around lines 13213 - 13218, Register parseTraeIncremental
in the rollout provider invocation/canonical list alongside the existing Trae
symbols so the rollout dispatcher invokes it and collects Trae entitlement
snapshots; keep the existing export unchanged.

Source: Coding guidelines

Comment thread src/lib/rollout.js
Comment on lines +13252 to +13257
async function parseTraeIncremental({ traHome, storagePath, cursors, queuePath, onProgress } = {}) {
await ensureDir(path.dirname(queuePath));
const targetPath = storagePath ?? resolveTraeStoragePath(process.env);
if (!targetPath || !fssync.existsSync(targetPath)) {
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

traHome parameter is accepted but never used.

The destructured traHome is dead — targetPath always falls back to resolveTraeStoragePath(process.env), ignoring any traHome the caller supplies and bypassing any injected env for testing. If a caller resolves traHome once (e.g. to avoid recomputation or to override for tests), that value is silently discarded.

♻️ Proposed fix
-async function parseTraeIncremental({ traHome, storagePath, cursors, queuePath, onProgress } = {}) {
+async function parseTraeIncremental({ traHome, storagePath, cursors, queuePath, onProgress, env } = {}) {
   await ensureDir(path.dirname(queuePath));
-  const targetPath = storagePath ?? resolveTraeStoragePath(process.env);
+  const targetPath =
+    storagePath ??
+    (traHome
+      ? path.join(traHome, "User", "globalStorage", "storage.json")
+      : resolveTraeStoragePath(env || process.env));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function parseTraeIncremental({ traHome, storagePath, cursors, queuePath, onProgress } = {}) {
await ensureDir(path.dirname(queuePath));
const targetPath = storagePath ?? resolveTraeStoragePath(process.env);
if (!targetPath || !fssync.existsSync(targetPath)) {
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
}
async function parseTraeIncremental({ traHome, storagePath, cursors, queuePath, onProgress, env } = {}) {
await ensureDir(path.dirname(queuePath));
const targetPath =
storagePath ??
(traHome
? path.join(traHome, "User", "globalStorage", "storage.json")
: resolveTraeStoragePath(env || process.env));
if (!targetPath || !fssync.existsSync(targetPath)) {
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/rollout.js` around lines 13252 - 13257, Update parseTraeIncremental
to use the supplied traHome when resolving the fallback target path, rather than
always calling resolveTraeStoragePath with process.env. Preserve storagePath
precedence and the existing early return when the resolved target is missing.

Comment thread src/lib/rollout.js
Comment on lines +13252 to +13329
async function parseTraeIncremental({ traHome, storagePath, cursors, queuePath, onProgress } = {}) {
await ensureDir(path.dirname(queuePath));
const targetPath = storagePath ?? resolveTraeStoragePath(process.env);
if (!targetPath || !fssync.existsSync(targetPath)) {
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
}

const cursorState = cursors?.trae && typeof cursors.trae === "object" ? cursors.trae : {};
const lastMtime = cursorState.lastMtime || 0;

const stat = await fs.stat(targetPath).catch(() => null);
if (!stat || stat.mtimeMs <= lastMtime) {
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
}

const raw = await fs.readFile(targetPath, "utf8");
let storage;
try {
storage = JSON.parse(raw);
} catch {
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
}

const serverKey = "iCubeServerData://icube.cloudide";
const serverData = storage[serverKey];
if (!serverData) {
cursorState.lastMtime = stat.mtimeMs;
cursorState.updatedAt = new Date().toISOString();
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
}

let ent;
try {
ent = typeof serverData === "string" ? JSON.parse(serverData) : serverData;
} catch {
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
}

const entitlementInfo = ent.entitlementInfo || {};
const detail = entitlementInfo.detail || {};
const hourStart = new Date(
new Date().toISOString().slice(0, 13) + ":00:00.000Z"
).toISOString();

const model = "trae-" + (entitlementInfo.identityStr || "unknown").toLowerCase();

const queueLine = JSON.stringify({
source: "trae",
model,
hour_start: hourStart,
input_tokens: 0,
cached_input_tokens: 0,
cache_creation_input_tokens: 0,
output_tokens: 0,
reasoning_output_tokens: 0,
total_tokens: 0,
billable_total_tokens: 0,
conversation_count: 0,
trae_entitlement: {
identity: entitlementInfo.identityStr,
identity_code: entitlementInfo.identity,
has_package: entitlementInfo.hasPackage,
is_dollar_billing: entitlementInfo.isDollarUsageBilling,
pro_period: entitlementInfo.proPeriod,
enable_solo_builder: entitlementInfo.enableSoloBuilder,
enable_solo_coder: entitlementInfo.enableSoloCoder,
fast_request_per: detail.fastRequestPer,
in_waitlist: detail.inWaitlist,
},
});

await fs.appendFile(queuePath, queueLine + "\n");

cursorState.lastMtime = stat.mtimeMs;
cursorState.updatedAt = new Date().toISOString();

return { recordsProcessed: 1, eventsAggregated: 1, bucketsQueued: 1 };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Critical: cursors.trae mutation is never persisted back — entitlement snapshot re-queued on every run.

cursorState is derived from cursors?.trae, defaulting to a fresh {} when cursors.trae doesn't exist yet (Line 13259). All later mutations (Lines 13278-13280, 13325-13326) write to this local object, but it is never assigned back to cursors.trae. On the very first run, cursors.trae stays undefined forever, so lastMtime is always read as 0 on the next invocation and the stat.mtimeMs <= lastMtime short-circuit (Line 13263) never triggers — the same entitlement snapshot gets re-parsed and re-appended to the queue on every call. parsePiIncremental avoids this by explicitly reassigning cursors.pi = {...} (Lines 9825-9830); parseTraeIncremental needs the same treatment.

🐛 Proposed fix
   if (!serverData) {
     cursorState.lastMtime = stat.mtimeMs;
     cursorState.updatedAt = new Date().toISOString();
+    cursors.trae = cursorState;
     return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
   }
@@
   cursorState.lastMtime = stat.mtimeMs;
   cursorState.updatedAt = new Date().toISOString();
+  cursors.trae = cursorState;

   return { recordsProcessed: 1, eventsAggregated: 1, bucketsQueued: 1 };
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 13266-13266: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFile(targetPath, "utf8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/rollout.js` around lines 13252 - 13329, Update parseTraeIncremental
so its cursor mutations are persisted to the caller by assigning the updated
cursorState back to cursors.trae, following the existing parsePiIncremental
pattern. Ensure this assignment occurs before returning after the cursor is
updated, including the no-server-data path, while preserving the current mtime
comparison and queue behavior.

Comment thread src/lib/rollout.js
Comment on lines +13275 to +13296
const serverKey = "iCubeServerData://icube.cloudide";
const serverData = storage[serverKey];
if (!serverData) {
cursorState.lastMtime = stat.mtimeMs;
cursorState.updatedAt = new Date().toISOString();
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
}

let ent;
try {
ent = typeof serverData === "string" ? JSON.parse(serverData) : serverData;
} catch {
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
}

const entitlementInfo = ent.entitlementInfo || {};
const detail = entitlementInfo.detail || {};
const hourStart = new Date(
new Date().toISOString().slice(0, 13) + ":00:00.000Z"
).toISOString();

const model = "trae-" + (entitlementInfo.identityStr || "unknown").toLowerCase();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

No guard for missing entitlementInfo inside serverData.

Line 13277-13281 handles the case where serverData itself is absent, but if serverData parses fine yet lacks entitlementInfo, execution falls through to entitlementInfo = {} (Line 13290) and still queues a synthetic trae-unknown entry with all entitlement fields undefined. Consider returning early (and persisting the cursor) when entitlementInfo is missing, mirroring the serverData check above.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/rollout.js` around lines 13275 - 13296, Update the entitlement
processing around entitlementInfo so parsed serverData without a valid
entitlementInfo returns early, persists cursorState.lastMtime and
cursorState.updatedAt, and reports zero processing counts like the missing
serverData path. Only continue to derive detail and the model when
entitlementInfo is present, avoiding synthetic trae-unknown entries.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

An unexpected error occurred while generating fixes: Not Found - https://docs.github.com/rest/git/refs#get-a-reference

@xiufengsun

xiufengsun commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Code review

Thanks for adding Trae SOLO support! However, this PR currently fails CI and introduces a few regressions that need to be fixed before merging:

  1. Regression in Pi provider routing logic (src/lib/rollout.js)
    Removing piSourceForProvider and hardcoding the source to pi breaks source isolation for Pi sub-providers (e.g. pi-anthropic, pi-copilot) and causes existing tests in test + validate + build to fail. Please revert changes to Pi logic so this PR remains surgical to Trae SOLO only.

  2. Incomplete provider integration
    While parseTraeIncremental is defined and exported in src/lib/rollout.js, it is not yet registered or wired into src/commands/sync.js, src/commands/init.js, or src/commands/status.js.

  3. Missing unit tests
    Please add unit tests covering parseTraeIncremental and path resolution logic in test/ to ensure long-term stability.

Please update the branch once these items are addressed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants