Skip to content

fix(copilot): display device code in UI and prevent running state reset during auth - #191

Open
cong91 wants to merge 471 commits into
heyhuynhgiabuu:mainfrom
cong91:main
Open

fix(copilot): display device code in UI and prevent running state reset during auth#191
cong91 wants to merge 471 commits into
heyhuynhgiabuu:mainfrom
cong91:main

Conversation

@cong91

@cong91 cong91 commented Mar 3, 2026

Copy link
Copy Markdown
  • Fix extract_user_code() to scan all lines unconditionally for XXXX-XXXX pattern; previous logic missed copilot-api v0.7+ format 'Please enter the code' which contains 'the' and no colon
  • Fix check_copilot_health to preserve in-memory running flag instead of deriving it from HTTP; server does not listen during GitHub device flow so HTTP failure was incorrectly marking the process as stopped
  • Skip frontend health poll while device auth message is active to prevent running state from being overwritten during auth
  • Add CopilotAuthInfo struct (user_code, verification_uri, raw_message)
  • Update onCopilotAuthRequired event callback type from string to CopilotAuthInfo
  • Show device code prominently in UI with Copy button
image

heyhuynhgiabuu and others added 30 commits December 21, 2025 21:22
- Version 0.2.0 reflects major refactor (lib.rs → modules)
- Add prefix support to API key types and forms
- Fix auth file download query param
- Make version display dynamic in Settings.tsx
- Rebuilt all platform binaries (macOS ARM64/x86_64, Linux, Windows)
- return only added request from history command to cut payload
- add centralized request store and debounce request handling
- optimize savings/dashboard/analytics charts for memory on Windows
## Split Storage
- Add Aggregate struct for cumulative analytics (never trimmed)
- Split history.json (500 requests) from aggregate.json (all-time stats)
- Auto-migrate old format on first run
- Sync token data from proxy to aggregate
- Fix success rate calculation

## UI Components
- Add new Sidebar component
- Add ECharts-based chart components (Bar, Donut, Gauge, Heatmap)

## Page Refactors
- Refactor Analytics, Dashboard, LogViewer, Settings pages
- Add GitHub Copilot to OAuth Excluded Models providers

## Backend
- Add get_aggregate_path config function
- Add migration function called on startup
- Update log watcher to use split storage
- Update get_usage_stats to use aggregate data

## Dependencies
- Update CLIProxyAPI submodule to latest version
- Update binaries for all platforms
- Add pin toggle button above dashboard navigation
- When pinned, sidebar stays expanded regardless of mouse hover
- When unpinned, sidebar collapses on mouse leave
- Uses bookmark/pushpin icon (filled when pinned, outline when not)
- Add atomic file writes (temp file + rename) to prevent corruption
- Add retry logic for Windows file locking issues (3 attempts)
- Add debug logging to track provider models on save/load/migration
- Improve error messages for better diagnostics

These changes should fix the issue where custom provider models disappear after app restart on Windows.
- Add reloadConfig() function to sync Tauri config state
- Call reloadConfig() after adding, removing, or saving models in ApiKeys
- This ensures Settings page shows up-to-date provider models

Fixes issue where model changes in ApiKeys page weren't reflected in Settings page.
Change model order from Haiku → Opus → Sonnet to Haiku → Sonnet → Opus for better capability-based ordering.
Add gemini-cli to providerIcons mapping to use correct Gemini logo instead of OpenAI logo.
Version bump to 0.3.3

UI Improvements:
- Fix sidebar overlap when pinned - add dynamic padding (pl-16 collapsed, pl-48 expanded)
- Standardize page backgrounds: bg-white dark:bg-gray-900 across all pages
- Add flex layouts to Settings and Welcome main elements
- Improve Copilot API Detection UI clarity for better user understanding

Files changed:
- package.json: 0.3.2 → 0.3.3
- src-tauri/tauri.conf.json: 0.3.1 → 0.3.3
- src-tauri/Cargo.toml: 0.3.0 → 0.3.3
- src/App.tsx: dynamic sidebar padding based on appStore.sidebarExpanded
- src/components/Sidebar.tsx: use appStore state instead of local state
- src/stores/app.ts: add sidebarExpanded signal
- src/pages/Settings.tsx: improve Copilot Detection UI
- src/pages/Welcome.tsx: add proper background and flex layout
- src/pages/Dashboard.tsx: bg-gray-50 → bg-white
- src/pages/Analytics.tsx: bg-gray-50 → bg-white
- src/pages/ApiKeys.tsx, AuthFiles.tsx, LogViewer.tsx: maintain consistent styling
- src-tauri/src/commands/config.rs: fix unused function warning
- CLIProxyAPI binaries: update to latest build

Resolves: heyhuynhgiabuu#55 (improved Copilot Detection explanation)
- Update CLIProxyAPI from v6.6.48 to v6.6.50
- Rebuild all platform binaries
- Fix heyhuynhgiabuu#60: correct command name mismatch (set_config_yaml -> save_config_yaml)
- Fix heyhuynhgiabuu#59: add event listener for 'Go to Settings' button navigation
- Bump version to 0.3.6 in package.json, tauri.conf.json, Cargo.toml
Features:
- Add per-model token breakdown columns (In/Out/Cache) to Analytics Model Usage table
- Persist cached token tracking across sessions in aggregate storage
- Update CLIProxyAPI to v6.6.52 with Antigravity/Gemini cached token parsing fix

Changes:
- Extended ModelStats and Aggregate structs with input/output/cached token fields
- Updated aggregate update logic to track cached tokens from requests
- Added responsive token breakdown columns to Analytics table (hidden on mobile)
- Rebuilt all platform binaries with CLIProxyAPI v6.6.52
- Fix Antigravity/Claude caching issue where context was consumed too quickly
- Rebuilt all platform binaries
Features:
- Add Proxy API Key setting (for client authentication, api-keys in YAML)
- Add Management API Key setting (for internal management API, secret-key in YAML)
- Both keys have show/hide toggle buttons, hidden by default for security

Bug Fixes:
- Fix copilot-api detection: prioritize npx over bunx (npx more reliable for downloads)
- Users with bun installed but bunx failing to download packages can now use npx fallback

Changes:
- Added proxyApiKey and managementKey fields to AppConfig with UI in Settings
- Added get_management_key() helper to dynamically load key from config
- Changed copilot start priority: globally installed > npx > bunx
- Add chart/list view toggle for Antigravity quota display
- Show percentage labels inside bars for quick scanning
- Round percentages to 1 decimal place for cleaner display
- Strip provider prefixes from model names to save space
- Add color-coded bars (green/yellow/red) based on quota thresholds
- Dynamic chart height based on model count
- Add model filtering grouped by category
…-quota

feat(quota): enhance QuotaWidget with chart view and improved UX
- Update CLIProxyAPI binaries to v6.6.55
- Add index field to image response for LiteLLM compatibility
- Fix multimodal assistant content handling across request handlers
- Sync version across package.json, Cargo.toml, and tauri.conf.json
…6.6.56

- Add export button to save usage stats to JSON file
- Add import button to restore usage stats from JSON backup
- Add Tauri fs plugin for file read/write operations
- Update capabilities for dialog save and fs permissions
- CLIProxyAPI v6.6.56 brings import/export API and enhanced deduplication
- Add scripts/download-binaries.sh to fetch CLIProxyAPI from releases
- Update build.rs to auto-download missing binaries during build
- Add binaries to .gitignore (downloaded at build time, not tracked)
BREAKING: Repository history rewritten to remove large binaries.

Changes:
- Remove CLIProxyAPI submodule (no longer needed)
- Remove committed binaries from git tracking
- Add download script to fetch binaries from CLIProxyAPI releases
- Update build.rs to auto-download binaries during build
- Binaries now downloaded at build time, not stored in repo

Fixes heyhuynhgiabuu#70
Add explicit text colors for select, option, and optgroup elements
to fix white-on-white text issue on Ubuntu/GTK systems.

Fixes heyhuynhgiabuu#68
heyhuynhgiabuu and others added 28 commits February 21, 2026 15:13
Backend hardening (TDD, 10 new tests):
- config.rs: extract load_config_from_path, migrate_config, save_config_to_path
- commands/proxy.rs: extract env_proxy_for_url, normalize_system_proxy helpers
- commands/config.rs: scoped routing strategy replacement, disk IO before mutex

Docs:
- AGENTS.md: update for ProxyPal (stack, structure, gotchas)
- Remove stale design docs (DESIGN_SPLIT_STORAGE, WORK_SUMMARY, minimax research)
Extract monolithic lib.rs into 15 command modules + 3 helper modules:
- commands/: agents, api_keys, auth, auth_files, copilot, health,
  logs, models, proxy, quota, settings, usage (+ existing config,
  cloudflare, ssh)
- helpers/: history, log_watcher, migration

Internal decomposition of God functions:
- proxy.rs: start_proxy (659 → ~180 lines) via 12 YAML builder helpers
- agents.rs: configure_cli_agent (710 → ~50 lines) via 4 per-agent helpers

Fix 3 pre-existing bugs in log_watcher.rs:
- Operator precedence in log format detection
- Log rotation: reopen file at path instead of seeking stale FD
- DST panic: fallback on ambiguous local time conversion

Sidecar binary management:
- Remove tracked binaries from git (now gitignored)
- Add cross-platform scripts/update-sidecar.mjs (Node 18+)
- Works on macOS, Linux, Windows (tar/zip extraction)
- Dashboard.tsx: 2,923 → 931 lines (-68.1%)
  - Extract 5 quota widgets → components/dashboard/quotas/
  - Extract OnboardingChecklist + ProviderSection → components/dashboard/

- Settings.tsx: 5,785 → 755 lines (-87.0%)
  - Extract 10 tab components → components/settings/
  - Fix forceModelMappings stale state (createEffect on proxy status)

- ApiKeys.tsx: 1,980 → 193 lines (-90.2%)
  - Extract 5 provider tabs → components/api-keys/

- tauri.ts: 1,536 → 19 domain modules in lib/tauri/ with barrel index.ts

- Polish: remove 5 debug console calls, add toasts to 6 silent catches,
  fix 2 any types (AuthFiles.tsx, BarChart.tsx)

56 files changed, 11984 insertions(+), 10866 deletions(-)
- build.rs: check magic bytes (reject gzip 1f8b), re-download if invalid,
  panic in CI if binary missing/corrupted
- update-sidecar.mjs: validateBinary() after install, findBinary() skips
  archive files (.tar.gz, .zip), exact name matching only
- release.yml: recursive find fallback with archive exclusion

Prevents 'cannot execute binary file' (exit 126) when archive is
accidentally used as sidecar binary.
Backend (lib.rs: 8,263 → 524 lines, -93.7%):
- Extract 15 command modules: api_keys, quota, copilot, models, usage,
  auth, agents, proxy, health, settings, auth_files, logs, config, ssh,
  cloudflare
- Extract 3 helper modules: history, migration, log_watcher
- Internal decomposition: start_proxy (659→180 lines),
  configure_cli_agent (710→50 lines)
- Fix log_watcher bugs: operator precedence, log rotation, DST panic
- Fix YAML indentation in build_payload_section raw string literal
- Backend hardening: config loading safety, proxy detection robustness,
  save+YAML sync with scoped routing strategy

Frontend (Dashboard 2,923→931, Settings 5,785→755, ApiKeys 1,980→193):
- Extract 20+ components to components/dashboard/ and components/settings/
- Split tauri.ts (1,536 lines) into 19 domain modules with barrel index
- Fix AmpSettings forceModelMappings stale state (missing onMount)
- Remove 5 debug console calls, add toasts to 6 silent catches
- Fix 2 `any` types (AuthFiles, BarChart)

Tooling:
- Add oxlint 1.49 + oxfmt 0.34 + @nkzw/oxlint-config (SolidJS overrides)
- Add tsgo optional fallback (check:ts) + parallel checks (check:parallel)
- CI: single parallel check step replaces 3 serial steps

Sidecar:
- Gitignore binaries, add cross-platform update-sidecar.mjs script
- Add build.rs magic byte validation against gzip archives
- Release workflow: recursive find fallback for binary extraction
Node.js 20 in CI cannot natively import .ts files
(ERR_UNKNOWN_FILE_EXTENSION). Renamed config to .mjs and added
explicit -c flag to lint scripts since oxlint only auto-detects
.ts config filenames.
build.rs now checks PROXYPAL_SKIP_SIDECAR env var to create a
placeholder binary instead of panicking when sidecar is missing.
CI check workflow sets this var for cargo check (no bundling needed).
Removes broken placeholder step that created empty file failing
magic byte validation.
Missing #[cfg(target_os = "windows")] use std::os::windows::process::CommandExt
caused compilation failure on Windows CI. The import was lost during
lib.rs extraction to agents.rs.
Upstream changes:
- fix(gemini): update min Thinking value, add Gemini 3.1 Pro Preview
- feature(proxy): special handling for client cancellations
- Improved error handling for canceled requests (#1675)
- agents.rs: expanded is_multimodal check to include gpt-4o, gpt-4.1,
  gpt-5.x, o1/o3/o4, claude-*, and copilot-gpt-4 model prefixes
- opencode.json: added modalities config to 37 models (17 GPT + 20 Claude)
  that were missing input: [text, image] support

Fixes heyhuynhgiabuu#168
Device-code login:
- Backend: add get_device_code command in auth.rs calling management
  API without is_webui param to trigger device-code flow
- Frontend: DeviceCodeModal with user code display, copy-to-clipboard,
  verification URL, countdown timer, and auto-polling
- ProviderSection: device-code button (phone icon) for openai/qwen
- Dashboard: wire device code signals, modal, and auth refresh

Sidecar v6.8.28-1 (from v6.8.26-0):
- Codex device-code login flow (v6.8.27-0)
- File input passthrough to Codex/Claude (v6.8.27-0)
- Gemini usage conversion fix (v6.8.27-0)
- Qwen rate limiting and quota error handling (v6.8.28-0)
- Codex responses compaction compatibility (v6.8.28-0)
- Auth auto-refresh interval fix (v6.8.28-0)
- Copilot user email/name persistence (v6.8.28-1)
…hgiabuu#177)

Root cause: reqwest auto-uses system proxy settings, routing local
127.0.0.1 management API requests through the user's proxy (e.g.
http://127.0.0.1:2080/) causing 502 Bad Gateway errors.

Changes:
- build_management_client(): add .no_proxy() to reqwest builder,
  fixing ALL management API callers (quota, models, usage, auth, etc.)
- start_proxy(): replace fixed 500ms sleep with health-check poll loop
  (200ms intervals, 5s timeout) for reliable proxy readiness detection
- start_proxy(): use build_management_client() instead of
  reqwest::Client::new() for settings sync calls
heyhuynhgiabuu#176)

- Deep AMP slot: gpt-5.2-codex → gpt-5.3-codex (models.ts)
- Added gpt-5.3-codex and gpt-5.3-codex-spark to model dropdowns
  (AdvancedSettings.tsx, Settings.tsx)
- Added gpt-5.3-codex-spark to GPT5_BASE_MODELS constant (lib.rs)
- Bumped version to 0.4.4
…heyhuynhgiabuu#180)

All reqwest::Client calls to localhost/127.0.0.1 management API now use
no_proxy() to prevent system proxy from intercepting local requests.
This fixes Gemini verification hanging when a system proxy is configured.

Affected files: models.rs (2), health.rs (1), proxy.rs (1),
auth.rs (4), usage.rs (3), copilot.rs (4) — 15 calls total.
External API calls (quota.rs, models.rs external) unchanged.
…buu#172)

sync_usage_from_proxy was overwriting all-time totals with session data
using assignment (=). On restart, session counters are zero, causing
all-time cost/tokens to reset.

Fixed by:
- Using max() in sync to never go backwards on all-time totals
- get_usage_stats now always uses aggregate for token totals instead
  of replacing with live session data when proxy is running
Changes since v6.8.28-1:
- gemini-3.1-flash-image model support
- New Kiro models + IDC auth + endpoint improvements
- Sonnet 4.6 model alias
- Fix auth refresh concurrency (prevent refresh storms)
- Fix Codex usage_limit_reached retry_after handling
- Fix Antigravity tool_result image parts + model fallback
- Redesigned fingerprint system
- Bump version to 0.4.6
…abuu#186)

- iFlow: changed test model from gemini-claude-sonnet-4-5 to qwen3-coder-plus
  (Claude-routed model can't be used with iFlow auth, causing 502 Bad Gateway)
- Kimi: added missing test model kimi-k2.5
  (was previously unhandled, would show 'Unknown provider' error)

Closes heyhuynhgiabuu#186
…uu#184)

- Added missing kimi/deepseek filename detection when uploading auth files
- Previously defaulted to 'claude' provider, causing wrong test model selection
- OAuth model alias configs + registry API endpoints
- Claude 1M header support + beta merging fix
- Claude cache-control fixes
- thinking.type=auto support, thinkingBudget default 64000
- Claude Code 2.1.63 fingerprint alignment
- Multiple bug fixes (#1711, #1716, #1758)
…l stats

- Add token total computation in sync_usage_from_proxy_blocking
- Use .max() guard for total_tokens_cached to handle proxy restarts
- Add eprintln! error logging on 4 silent failure paths
- Align async sync_usage_from_proxy to use .max() consistently
…tion

Changed test model from gemini-claude-sonnet-4-5 (Gemini-routed) to
claude-sonnet-4-5 (native Anthropic) so test connection works with
Claude auth files instead of returning 502 unknown provider.
…tus changes

Move model loading from onMount (runs once) to createEffect that
watches proxy running status. This ensures models are re-fetched:
- After proxy restarts (e.g., from AMP save)
- When proxy starts after Settings page is already mounted
- With 500ms delay to let sidecar fully load auth files
Fixes CI failure from oxfmt --check in v0.4.7 release.
- Blue/cyan themed banner promoting pikaai.xyz
- Lightning bolt + AI nodes SVG logo
- Placed below OpenCodeKit banner on Dashboard
…et during auth

- Fix extract_user_code() to scan all lines unconditionally for XXXX-XXXX
  pattern; previous logic missed copilot-api v0.7+ format 'Please enter
  the code' which contains 'the' and no colon
- Fix check_copilot_health to preserve in-memory running flag instead of
  deriving it from HTTP; server does not listen during GitHub device flow
  so HTTP failure was incorrectly marking the process as stopped
- Skip frontend health poll while device auth message is active to prevent
  running state from being overwritten during auth
- Add CopilotAuthInfo struct (user_code, verification_uri, raw_message)
- Update onCopilotAuthRequired event callback type from string to CopilotAuthInfo
- Show device code prominently in UI with Copy button
…Hub Copilot

Adds an embedded axum HTTP server (port 4142 by default) that proxies
/v1/embeddings requests to copilot-api. CLIProxyAPI does not support
embeddings endpoints natively, so this sidecar fills the gap.

- New embeddings_proxy.rs: axum server with /v1/embeddings and /v1/models
- CopilotConfig: add embeddingsPort field (default 4142)
- CopilotStatus: expose embeddingsPort to frontend
- AppState: store shutdown handle (oneshot::Sender) for clean teardown
- copilot.rs: start proxy on copilot-api start, stop on stop
- CopilotCard.tsx: show embeddings endpoint when connected; add port setting
- Supported models: text-embedding-3-small, text-embedding-3-small-inference,
  text-embedding-ada-002 (all free quota, no policy restrictions)

@heyhuynhgiabuu heyhuynhgiabuu left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

PR #191 Review

Scope: Copilot device code auth fix + embeddings proxy feature

Good Changes

Device code auth fix — well-motivated and correctly implemented:

  • extract_user_code() scans all lines for XXXX-XXXX pattern — fixes copilot-api v0.7+ format
  • check_copilot_health preserves in-memory running flag — during device auth, server is not listening yet, so HTTP failure was incorrectly marking process as stopped
  • Frontend skips health poll while authMessage() is active
  • CopilotAuthInfo struct replaces raw string — clean data model
  • Device code shown prominently with Copy button — great UX improvement
  • Multi-line output accumulation + auth_emitted AtomicBool prevents duplicates

Embeddings proxy — fills a real gap (CLIProxyAPI does not support /v1/embeddings):

  • Clean axum server with graceful shutdown via oneshot::Sender
  • Non-fatal failure — copilot-api still works if proxy cannot bind
  • Configurable port with serde defaults
  • Frontend shows endpoint info when connected

Issues to Address

1. Unbounded body read — security concern
embeddings_proxy.rs:88: axum::body::to_bytes(body, usize::MAX) — a malicious or buggy client could send a multi-GB request and exhaust memory. Should cap at a reasonable limit:

let bytes = axum::body::to_bytes(body, 10 * 1024 * 1024) // 10MB

2. reqwest::Client::new() per request — performance
embeddings_proxy.rs:77: Creates a new HTTP client for every embeddings request, skipping connection pooling. Should store the client in ProxyConfig (axum State):

struct ProxyConfig {
    upstream_base: String,
    client: reqwest::Client,
}

3. Accumulated output grows indefinitely
copilot.rs ~line 232: accumulated_output string is never cleared or bounded. If copilot-api is verbose, this grows without limit. Should clear after auth_emitted fires, or cap at ~10KB.

4. New dependency: axum 0.8.8
Brings in axum, axum-core, matchit, httpdate, and more of hyper/tower. Meaningful binary size increase for a simple HTTP proxy. Not a blocker, but worth noting.

5. Hardcoded English strings
CopilotCard.tsx: "Embeddings endpoint", "Embeddings proxy port", "Models: text-embedding-3-small...", "Copy", "Copied!" — should be in the i18n catalog.

6. No clipboard error handling
CopilotCard.tsx:handleCopyCode: navigator.clipboard.writeText can throw in certain contexts. Should wrap in try/catch.

7. Cargo.lock version mismatch
PR sets version to 0.4.8, main is now 0.4.9. Needs rebase.

Suggestion

These two changes should ideally be separate PRs — the device code fix is a bug fix, while the embeddings proxy is a new feature with a new dependency. Mixing them makes it harder to review and revert independently.

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.

10 participants