Skip to content

Commit 675c353

Browse files
authored
Merge pull request #186 from cnjack/codex/grok-unified-auth
feat: add unified provider account authentication
2 parents 73567b8 + 0bb1db3 commit 675c353

95 files changed

Lines changed: 12470 additions & 314 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Added
11+
- **Unified Provider account sign-in.** Settings and first-run setup can now authenticate OpenAI through ChatGPT/Codex, xAI through Grok, and GitHub Copilot through one device-code account flow, while preserving API-key providers. Providers bind to a default or explicit local account and expose connected, reauthentication, and multi-account management states.
12+
- GitHub Copilot requests keep one stable session interaction while classifying tool continuations and delegated agents as agent-initiated, avoiding accidental extra premium interactions.
13+
- Managed ChatGPT/Codex, xAI, and GitHub Copilot Providers now browse the selected account's live model catalog. Enabled account-scoped models survive restart, and Copilot routes OpenAI models through Responses while retaining Chat Completions for other advertised vendors.
1114
- **Provider-backed image generation.** Configure a global Image Model independently from the chat model, then use `generate_image` from normal-mode TUI, Web, Desktop, or ACP sessions. The first release supports OpenAI-compatible Images endpoints, BigModel CogView, and Alibaba Token Plan Wan 2.7 models.
15+
- Grok account sign-in now exposes the official `grok-imagine-image` and `grok-imagine-image-quality` models through the Image Model role with dispatch-time OAuth credentials; xAI video entries are kept out of unsupported chat/image surfaces.
1216
- **Generated images as managed Artifacts.** Results are verified, stored outside the workspace under the session, persisted for replay, and shown as lifecycle-aware image cards in Web/Desktop. TUI reports the local path and metadata; ACP degrades to metadata, resource links, or bounded inline images according to negotiated capabilities.
1317
- **Provider capability routing.** Settings now distinguishes chat, image generation, vision input, and provider-bound tools using the exact provider profile, endpoint, protocol, and model. It includes an Image Model picker, provider capability status, a BigModel Search MCP preset, and provider Web Search policy.
1418

1519
### Changed
20+
- Grok Imagine generation now uses xAI-native `aspect_ratio` and `resolution` controls (`1k`/`2k`) instead of forwarding OpenAI-style `size`; older common JCode sizes are normalized into the equivalent native controls before approval and dispatch.
1621
- **Ask User is now a bottom interaction dock.** Pending questions replace the composer and are presented one at a time with paging, recommended and multi-select options, custom answers, skip, submission progress, and retryable errors. Once answered, a compact receipt remains in the conversation timeline.
1722
- Pending Ask User calls no longer merge into activity groups, and both pending and resolved question surfaces align with the conversation gutter.
1823
- Fresh blank sessions hide task/session chrome until conversation work exists; loading and persisted sessions keep their controls.
1924

2025
### Fixed
26+
- Grok device sign-in now accepts xAI's official `accounts.x.ai/oauth2/device` verification page while retaining strict HTTPS, host, port, and user-info checks.
27+
- Provider Settings now shows the last successful account-scoped model catalog immediately when reopened, revalidates it in the background, and preserves it through transient refresh failures without allowing an older account request to overwrite newer results.
28+
- Managed Grok Image Models now pass the image-tool availability check without requiring an API key, so selecting a supported Grok Imagine model exposes `generate_image` to active normal-mode agents.
2129
- Provider configuration writes are serialized as reload → mutate → atomic save, reject stale snapshots, preserve secrets, and rebuild provider tools after keys, endpoints, or models change.
2230
- Session replay now restores provider operations, managed Artifacts, tool lifecycle, session modes, and per-session tool overrides without trusting dropped WebSocket events.
2331

2432
### Security
33+
- Managed Provider credentials are resolved immediately before dispatch, never returned by the Web API, and kept out of `config.json`. OAuth-backed providers pin their upstream endpoint, wire protocol, and protected headers; refreshes are singleflight, account writes are locked and atomic, device flows are bounded/cancellable, and invalid or reauthentication-required bindings fail closed.
2534
- Externally billable calls bind approval to an immutable provider/model/argument intent and idempotency key. Ask for approval and Auto require a fresh per-call decision; Full access is the only session-level preauthorization. Per-turn and per-session limits are reserved atomically and dispatch is durably journaled before the provider call.
2635
- Image downloads require HTTPS and enforce trusted-host, redirect, timeout, MIME, size, dimension, and pixel limits. Private and link-local destinations are rejected; generated files use owner-only directories/files and atomic persistence.
2736
- Security-sensitive session journals fail closed on malformed or invalid transitions, and logs/session metadata exclude credentials, complete prompts, signed URLs, provider response bodies, and image base64.

internal-doc/image-generation-architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
- BigModel `cogview-3-flash` 精确 capability rule;
2727
- Alibaba Token Plan `wan2.7-image` / `wan2.7-image-pro` 精确 rule 与专属同步 `token_plan_multimodal` adapter;
2828
- 全局 Image Model;
29-
- 条件注册的 `generate_image(prompt, size?)`P0 严格单图且请求 schema 不暴露 `count`
29+
- 根据所选 adapter 条件注册 `generate_image` 参数:通用端点使用 `size?`,xAI 使用 `aspect_ratio?` / `resolution?`P0 严格单图且请求 schema 不暴露 `count`
3030
- provider URL/base64 同步结果;
3131
- managed Artifact v2、本地回放、Web/Desktop 图片卡、TUI 路径、ACP 文本/resource-link 降级;
3232
- BigModel Search MCP preset,先完成 MCP secret mask/merge;

internal-doc/provider-tools-image-generation-prd.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,7 @@ provider 返回 URL、base64 或 async task 时,adapter 统一产出受限 byt
442442
{
443443
"prompt": "required string",
444444
"aspect_ratio": "optional provider-neutral enum",
445+
"resolution": "optional provider-native validated value",
445446
"size": "optional validated value",
446447
"quality": "optional enum"
447448
}
@@ -454,6 +455,7 @@ provider 返回 URL、base64 或 async task 时,adapter 统一产出受限 byt
454455
- 属于 approval class `billable_external`,不加入 `noApprovalNeeded`。Ask for approval 与 Auto 不得静默批准;Full access 在 runner 校验 typed intent 与工具身份后直接放行,不产生 ApprovalRequest;
455456
- 需要审批时,每次审批只对应一次 `(provider profile, endpoint profile, model, normalized args, idempotency key)` 请求,选项只有“仅本次/拒绝”;不提供独立的图片 session grant,Full access 是统一会话模式;
456457
- P0 schema 不暴露 `count`,请求固定 1,provider 结果必须恰好 1 张;返回 0 或多张均 fail closed;
458+
- schema 由所选 Image Model 的 capability 构造:xAI 只向 Agent 暴露原生 `aspect_ratio` / `resolution`,通用 OpenAI Images 与 Token Plan 继续暴露 `size`。旧版 xAI `size` 输入必须在审批前显式规范化,不得把两套几何字段同时发送;
457459
- 不默认暴露给 subagent,防止同一 prompt 并发重复计费;
458460
- 不自动跨 provider/model fallback;
459461
- 一个批准动作只生成一个 idempotency key。provider 已接受请求后,网络不确定性不得自动重复提交;只允许用同一个 key 查询/恢复既有 task;
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Unified provider authentication POC
2+
3+
Status: accepted for implementation
4+
Date: 2026-08-09
5+
6+
## Goal
7+
8+
Prove that JCode can bind a Provider to a managed account instead of copying a
9+
short-lived access token into `config.json`. The POC covers the three managed
10+
login methods already implemented by cc-switch:
11+
12+
| JCode login | Device authorization | Runtime API |
13+
| --- | --- | --- |
14+
| ChatGPT / Codex | `auth.openai.com/api/accounts/deviceauth/*`, then OAuth code exchange | OpenAI Responses at `chatgpt.com/backend-api/codex/responses` |
15+
| Grok / xAI | OIDC discovery plus OAuth 2.0 Device Authorization Grant | OpenAI Responses at `api.x.ai/v1/responses` |
16+
| GitHub Copilot | GitHub device flow, then GitHub-to-Copilot token exchange | Account catalog selects `/v1/responses` for OpenAI models and `/chat/completions` for other vendors |
17+
18+
API-key authentication remains supported and is the default for existing
19+
configuration. Managed login is opt-in and backward compatible.
20+
21+
## Evidence copied from cc-switch
22+
23+
The implementation contract is derived from the local checkout at
24+
`/Users/jack/workpath/opensource/cc-switch`, not from a generic OAuth
25+
assumption:
26+
27+
- Provider configuration stores only an account binding; token lookup happens
28+
immediately before each upstream request.
29+
- Access tokens are memory-only. Refresh tokens (or the long-lived GitHub
30+
token for Copilot) are durable, owner-only secrets.
31+
- Account lists and the default account are shared across Providers. A Provider
32+
may bind a specific account or follow the current default.
33+
- Refresh is serialized per account and checked again after taking the lock.
34+
- An invalid refresh marks the account as requiring reauthentication. It never
35+
falls back to an API key or another Provider silently.
36+
- Managed authentication pins the upstream origin, wire protocol and protected
37+
headers. User-supplied headers cannot replace them.
38+
39+
## POC boundaries
40+
41+
The automated POC is executable through unit and HTTP-handler tests without a
42+
developer account and never sends a live billable model request.
43+
44+
The test transport substitutes local servers for each fixed remote endpoint
45+
and proves:
46+
47+
1. start returns a public verification URL, user code, bounded expiry and a
48+
random opaque flow ID; the upstream device token is never returned;
49+
2. poll maps pending, slow-down, denied, expired and success responses into one
50+
public state machine;
51+
3. successful login persists the durable credential before publishing an
52+
in-memory access token;
53+
4. concurrent inference requests perform at most one refresh per account;
54+
5. refresh-token rotation uses compare-and-swap semantics and invalid refresh
55+
persists `requires_reauth`;
56+
6. a Provider binding contains only `method` and optional `account_id`;
57+
7. model requests resolve a fresh credential and inject protected headers at
58+
dispatch time;
59+
8. ChatGPT/xAI chat requests select Responses. Copilot resolves the live model
60+
vendor per account, using Responses for OpenAI models and Chat Completions
61+
for other advertised vendors;
62+
9. public flow responses, Provider bindings, durable-state fixtures and runtime
63+
credential serialization expose no access token, refresh token, GitHub
64+
token, authorization code or device token;
65+
10. the durable store is `0700`/`0600`, written through fsync plus atomic
66+
rename, and mutations are guarded across processes.
67+
11. redirects cannot forward a managed model body, bearer token, or protected
68+
header to a second origin;
69+
12. cancel and logout are linearized with authorization commit, including
70+
flows owned by a second manager process, and flow capacity is reserved
71+
before any authorization-server request;
72+
13. Copilot user, tool-continuation, and subagent requests receive the correct
73+
initiator/interaction headers while one session keeps a stable opaque
74+
interaction ID.
75+
14. managed model catalogs are fetched with the selected account credential,
76+
bounded to 1 MiB, filtered by provider visibility, and never expose an
77+
upstream response body on error. Explicitly enabled live chat models retain
78+
their wire protocol across restart;
79+
15. xAI image and video entries are separated from chat. The official managed
80+
xAI image profile pins `api.x.ai/v1/images/generations`, resolves its token
81+
only at dispatch, and reuses the billable-operation approval, journal,
82+
quota, Artifact, and safe-download pipeline. Its adapter emits native
83+
`aspect_ratio` and `resolution` fields and never mixes them with OpenAI
84+
`size` controls;
85+
16. the Web/Desktop Provider catalog uses a token-free, account-aware local
86+
stale-while-revalidate cache. It renders the last successful list before
87+
the live request completes, retains it on transient failure, and rejects
88+
stale responses from an earlier account or refresh generation.
89+
90+
## Accepted result
91+
92+
The POC is accepted when the focused auth, catalog, model transport and provider
93+
API tests pass without network access. A manual account-scoped smoke test also
94+
confirmed that xAI and GitHub Copilot return their live model catalogs, and that
95+
the connected xAI account can read the official image-generation catalog. A
96+
later, explicitly user-authorized one-shot smoke test also confirmed that the
97+
managed xAI OAuth credential can generate one image without a separate API key;
98+
the generated asset and credentials were not committed. That billable smoke
99+
used `grok-imagine-image`; the quality variant is covered by the same local wire
100+
contract but was not invoked live to avoid a second provider charge.
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# Unified provider authentication UI design
2+
3+
Status: implemented
4+
Date: 2026-08-09
5+
6+
## Design read
7+
8+
This is an incremental JCode Settings enhancement for developers. It should
9+
feel restrained, trustworthy and tool-like. It reuses the existing Provider
10+
cards, form hierarchy, tokens, controls and Heroicons; it does not introduce an
11+
Auth Center, another Settings tab, a new modal language or a component library.
12+
13+
Design dials: variance 4, motion 2, density 6.
14+
15+
## Information architecture
16+
17+
```text
18+
Settings
19+
└─ Providers
20+
├─ Model roles
21+
├─ Provider cards
22+
│ └─ Authentication summary and recovery action
23+
└─ Add / Edit provider
24+
├─ Provider
25+
├─ Authentication
26+
│ ├─ API key
27+
│ └─ Account login
28+
│ ├─ Device code panel
29+
│ ├─ Account binding
30+
│ └─ Account management disclosure
31+
├─ Model connection
32+
└─ Advanced
33+
```
34+
35+
Authentication choices are declared by the backend:
36+
37+
- OpenAI: **API key** or **Sign in with ChatGPT**;
38+
- xAI / Grok: **API key** or **Sign in with Grok**;
39+
- GitHub Copilot: **Sign in with GitHub**;
40+
- custom OpenAI-compatible Provider: **API key** only.
41+
42+
The UI must not infer these choices from Provider IDs.
43+
44+
## Form design
45+
46+
When there are two choices, the Authentication section uses the existing
47+
segmented control. A single choice renders as a compact section label and its
48+
control without a redundant selector.
49+
50+
API-key mode preserves the current password field and advanced endpoint/header
51+
controls. Account mode removes API-key, editable endpoint/header, and custom
52+
image-endpoint controls from the task, because the backend pins the chat route
53+
and image endpoints currently require a separate Provider API key.
54+
55+
Signed-out account mode shows one primary login action and short explanatory
56+
copy. Starting a login replaces that row inline with:
57+
58+
- provider name and “Waiting for authorization” status;
59+
- a large monospace user code with Copy action;
60+
- Open browser and Cancel actions;
61+
- a subdued expiry time;
62+
- an `aria-live` pending/error message.
63+
64+
No dialog is stacked on the Provider form.
65+
66+
## Connected accounts
67+
68+
After login, the primary row contains a `UserCircleIcon`, account login and
69+
method label. The binding select offers:
70+
71+
- “Use default account — alice@example.com”;
72+
- each usable explicit account;
73+
- expired accounts disabled and labelled “Sign in again.”
74+
75+
“Manage N accounts” expands in place. Rows expose Default, Bound and Needs
76+
sign-in chips as applicable, followed by Set default, Sign in again or Remove.
77+
Provider removal and account removal are separate actions. Remote avatars are
78+
not loaded.
79+
80+
## Provider-card summaries
81+
82+
- API key: “API key configured” — never “Connected” without a validation
83+
result;
84+
- managed and healthy: “Connected · alice@example.com · ChatGPT OAuth”;
85+
- follows default: include “Default account” in the accessible label;
86+
- missing account: warning token plus “Choose account”;
87+
- invalid refresh: destructive token plus “Sign in again.”
88+
89+
Changing or removing a global account immediately refreshes all affected card
90+
summaries.
91+
92+
## State machine
93+
94+
```mermaid
95+
stateDiagram-v2
96+
[*] --> Loading
97+
Loading --> SignedOut
98+
Loading --> Connected
99+
SignedOut --> Starting
100+
Starting --> Pending
101+
Pending --> Connected: authorized
102+
Pending --> SignedOut: cancel
103+
Pending --> Error: denied / expired / network
104+
Error --> Starting: retry
105+
Connected --> Pending: sign in again
106+
Connected --> Connected: bind or set default
107+
Connected --> SignedOut: logout all
108+
Connected --> NeedsAuth: bound account removed
109+
NeedsAuth --> Pending: sign in
110+
```
111+
112+
The polling timer is cleared on cancel, form close, tab change and unmount.
113+
Successful authorization refreshes auth status, Provider list, target catalog
114+
and the model picker.
115+
116+
## Responsive and accessibility behavior
117+
118+
- Authentication options wrap to a two-column grid and one column at narrow
119+
widths.
120+
- Text labels remain visible; meaning never depends on color or an icon.
121+
- Buttons retain visible focus rings and at least the existing JCode target
122+
height.
123+
- Copy and browser-open actions have accessible names.
124+
- Pending uses `role=status`; failures use `role=alert`.
125+
- Motion is limited to the existing spinner and disclosure transition, and
126+
respects reduced-motion behavior inherited from the app.
127+
128+
## Visual tokens
129+
130+
Reuse `INPUT`, `BTN_PRIMARY`, `BTN_SECONDARY`, `BTN_DANGER`, `ROW`, `LABEL`,
131+
`CHIP` and `Segmented` from the Settings atoms. Use only existing CSS custom
132+
properties and Heroicons (`KeyIcon`, `UserCircleIcon`, `ArrowPathIcon`,
133+
`ClipboardDocumentIcon`, `ArrowTopRightOnSquareIcon`,
134+
`ExclamationTriangleIcon`). Provider branding continues through
135+
`ProviderIcon`; no hand-written SVG or hard-coded status color is added.

0 commit comments

Comments
 (0)