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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- **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.
- GitHub Copilot requests keep one stable session interaction while classifying tool continuations and delegated agents as agent-initiated, avoiding accidental extra premium interactions.
- 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.
- **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.
- 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.
- **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.
- **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.

### Changed
- 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.
- **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.
- Pending Ask User calls no longer merge into activity groups, and both pending and resolved question surfaces align with the conversation gutter.
- Fresh blank sessions hide task/session chrome until conversation work exists; loading and persisted sessions keep their controls.

### Fixed
- 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.
- 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.
- 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.
- 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.
- Session replay now restores provider operations, managed Artifacts, tool lifecycle, session modes, and per-session tool overrides without trusting dropped WebSocket events.

### Security
- 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.
- 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.
- 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.
- 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.
Expand Down
2 changes: 1 addition & 1 deletion internal-doc/image-generation-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
- BigModel `cogview-3-flash` 精确 capability rule;
- Alibaba Token Plan `wan2.7-image` / `wan2.7-image-pro` 精确 rule 与专属同步 `token_plan_multimodal` adapter;
- 全局 Image Model;
- 条件注册的 `generate_image(prompt, size?)`,P0 严格单图且请求 schema 不暴露 `count`;
- 根据所选 adapter 条件注册 `generate_image` 参数:通用端点使用 `size?`,xAI 使用 `aspect_ratio?` / `resolution?`;P0 严格单图且请求 schema 不暴露 `count`;
- provider URL/base64 同步结果;
- managed Artifact v2、本地回放、Web/Desktop 图片卡、TUI 路径、ACP 文本/resource-link 降级;
- BigModel Search MCP preset,先完成 MCP secret mask/merge;
Expand Down
2 changes: 2 additions & 0 deletions internal-doc/provider-tools-image-generation-prd.md
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ provider 返回 URL、base64 或 async task 时,adapter 统一产出受限 byt
{
"prompt": "required string",
"aspect_ratio": "optional provider-neutral enum",
"resolution": "optional provider-native validated value",
"size": "optional validated value",
"quality": "optional enum"
}
Expand All @@ -454,6 +455,7 @@ provider 返回 URL、base64 或 async task 时,adapter 统一产出受限 byt
- 属于 approval class `billable_external`,不加入 `noApprovalNeeded`。Ask for approval 与 Auto 不得静默批准;Full access 在 runner 校验 typed intent 与工具身份后直接放行,不产生 ApprovalRequest;
- 需要审批时,每次审批只对应一次 `(provider profile, endpoint profile, model, normalized args, idempotency key)` 请求,选项只有“仅本次/拒绝”;不提供独立的图片 session grant,Full access 是统一会话模式;
- P0 schema 不暴露 `count`,请求固定 1,provider 结果必须恰好 1 张;返回 0 或多张均 fail closed;
- schema 由所选 Image Model 的 capability 构造:xAI 只向 Agent 暴露原生 `aspect_ratio` / `resolution`,通用 OpenAI Images 与 Token Plan 继续暴露 `size`。旧版 xAI `size` 输入必须在审批前显式规范化,不得把两套几何字段同时发送;
- 不默认暴露给 subagent,防止同一 prompt 并发重复计费;
- 不自动跨 provider/model fallback;
- 一个批准动作只生成一个 idempotency key。provider 已接受请求后,网络不确定性不得自动重复提交;只允许用同一个 key 查询/恢复既有 task;
Expand Down
100 changes: 100 additions & 0 deletions internal-doc/provider-unified-auth-poc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Unified provider authentication POC

Status: accepted for implementation
Date: 2026-08-09

## Goal

Prove that JCode can bind a Provider to a managed account instead of copying a
short-lived access token into `config.json`. The POC covers the three managed
login methods already implemented by cc-switch:

| JCode login | Device authorization | Runtime API |
| --- | --- | --- |
| ChatGPT / Codex | `auth.openai.com/api/accounts/deviceauth/*`, then OAuth code exchange | OpenAI Responses at `chatgpt.com/backend-api/codex/responses` |
| Grok / xAI | OIDC discovery plus OAuth 2.0 Device Authorization Grant | OpenAI Responses at `api.x.ai/v1/responses` |
| 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 |

API-key authentication remains supported and is the default for existing
configuration. Managed login is opt-in and backward compatible.

## Evidence copied from cc-switch

The implementation contract is derived from the local checkout at
`/Users/jack/workpath/opensource/cc-switch`, not from a generic OAuth
assumption:

- Provider configuration stores only an account binding; token lookup happens
immediately before each upstream request.
- Access tokens are memory-only. Refresh tokens (or the long-lived GitHub
token for Copilot) are durable, owner-only secrets.
- Account lists and the default account are shared across Providers. A Provider
may bind a specific account or follow the current default.
- Refresh is serialized per account and checked again after taking the lock.
- An invalid refresh marks the account as requiring reauthentication. It never
falls back to an API key or another Provider silently.
- Managed authentication pins the upstream origin, wire protocol and protected
headers. User-supplied headers cannot replace them.

## POC boundaries

The automated POC is executable through unit and HTTP-handler tests without a
developer account and never sends a live billable model request.

The test transport substitutes local servers for each fixed remote endpoint
and proves:

1. start returns a public verification URL, user code, bounded expiry and a
random opaque flow ID; the upstream device token is never returned;
2. poll maps pending, slow-down, denied, expired and success responses into one
public state machine;
3. successful login persists the durable credential before publishing an
in-memory access token;
4. concurrent inference requests perform at most one refresh per account;
5. refresh-token rotation uses compare-and-swap semantics and invalid refresh
persists `requires_reauth`;
6. a Provider binding contains only `method` and optional `account_id`;
7. model requests resolve a fresh credential and inject protected headers at
dispatch time;
8. ChatGPT/xAI chat requests select Responses. Copilot resolves the live model
vendor per account, using Responses for OpenAI models and Chat Completions
for other advertised vendors;
9. public flow responses, Provider bindings, durable-state fixtures and runtime
credential serialization expose no access token, refresh token, GitHub
token, authorization code or device token;
10. the durable store is `0700`/`0600`, written through fsync plus atomic
rename, and mutations are guarded across processes.
11. redirects cannot forward a managed model body, bearer token, or protected
header to a second origin;
12. cancel and logout are linearized with authorization commit, including
flows owned by a second manager process, and flow capacity is reserved
before any authorization-server request;
13. Copilot user, tool-continuation, and subagent requests receive the correct
initiator/interaction headers while one session keeps a stable opaque
interaction ID.
14. managed model catalogs are fetched with the selected account credential,
bounded to 1 MiB, filtered by provider visibility, and never expose an
upstream response body on error. Explicitly enabled live chat models retain
their wire protocol across restart;
15. xAI image and video entries are separated from chat. The official managed
xAI image profile pins `api.x.ai/v1/images/generations`, resolves its token
only at dispatch, and reuses the billable-operation approval, journal,
quota, Artifact, and safe-download pipeline. Its adapter emits native
`aspect_ratio` and `resolution` fields and never mixes them with OpenAI
`size` controls;
16. the Web/Desktop Provider catalog uses a token-free, account-aware local
stale-while-revalidate cache. It renders the last successful list before
the live request completes, retains it on transient failure, and rejects
stale responses from an earlier account or refresh generation.

## Accepted result

The POC is accepted when the focused auth, catalog, model transport and provider
API tests pass without network access. A manual account-scoped smoke test also
confirmed that xAI and GitHub Copilot return their live model catalogs, and that
the connected xAI account can read the official image-generation catalog. A
later, explicitly user-authorized one-shot smoke test also confirmed that the
managed xAI OAuth credential can generate one image without a separate API key;
the generated asset and credentials were not committed. That billable smoke
used `grok-imagine-image`; the quality variant is covered by the same local wire
contract but was not invoked live to avoid a second provider charge.
135 changes: 135 additions & 0 deletions internal-doc/provider-unified-auth-ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Unified provider authentication UI design

Status: implemented
Date: 2026-08-09

## Design read

This is an incremental JCode Settings enhancement for developers. It should
feel restrained, trustworthy and tool-like. It reuses the existing Provider
cards, form hierarchy, tokens, controls and Heroicons; it does not introduce an
Auth Center, another Settings tab, a new modal language or a component library.

Design dials: variance 4, motion 2, density 6.

## Information architecture

```text
Settings
└─ Providers
├─ Model roles
├─ Provider cards
│ └─ Authentication summary and recovery action
└─ Add / Edit provider
├─ Provider
├─ Authentication
│ ├─ API key
│ └─ Account login
│ ├─ Device code panel
│ ├─ Account binding
│ └─ Account management disclosure
├─ Model connection
└─ Advanced
```

Authentication choices are declared by the backend:

- OpenAI: **API key** or **Sign in with ChatGPT**;
- xAI / Grok: **API key** or **Sign in with Grok**;
- GitHub Copilot: **Sign in with GitHub**;
- custom OpenAI-compatible Provider: **API key** only.

The UI must not infer these choices from Provider IDs.

## Form design

When there are two choices, the Authentication section uses the existing
segmented control. A single choice renders as a compact section label and its
control without a redundant selector.

API-key mode preserves the current password field and advanced endpoint/header
controls. Account mode removes API-key, editable endpoint/header, and custom
image-endpoint controls from the task, because the backend pins the chat route
and image endpoints currently require a separate Provider API key.

Signed-out account mode shows one primary login action and short explanatory
copy. Starting a login replaces that row inline with:

- provider name and “Waiting for authorization” status;
- a large monospace user code with Copy action;
- Open browser and Cancel actions;
- a subdued expiry time;
- an `aria-live` pending/error message.

No dialog is stacked on the Provider form.

## Connected accounts

After login, the primary row contains a `UserCircleIcon`, account login and
method label. The binding select offers:

- “Use default account — alice@example.com”;
- each usable explicit account;
- expired accounts disabled and labelled “Sign in again.”

“Manage N accounts” expands in place. Rows expose Default, Bound and Needs
sign-in chips as applicable, followed by Set default, Sign in again or Remove.
Provider removal and account removal are separate actions. Remote avatars are
not loaded.

## Provider-card summaries

- API key: “API key configured” — never “Connected” without a validation
result;
- managed and healthy: “Connected · alice@example.com · ChatGPT OAuth”;
- follows default: include “Default account” in the accessible label;
- missing account: warning token plus “Choose account”;
- invalid refresh: destructive token plus “Sign in again.”

Changing or removing a global account immediately refreshes all affected card
summaries.

## State machine

```mermaid
stateDiagram-v2
[*] --> Loading
Loading --> SignedOut
Loading --> Connected
SignedOut --> Starting
Starting --> Pending
Pending --> Connected: authorized
Pending --> SignedOut: cancel
Pending --> Error: denied / expired / network
Error --> Starting: retry
Connected --> Pending: sign in again
Connected --> Connected: bind or set default
Connected --> SignedOut: logout all
Connected --> NeedsAuth: bound account removed
NeedsAuth --> Pending: sign in
```

The polling timer is cleared on cancel, form close, tab change and unmount.
Successful authorization refreshes auth status, Provider list, target catalog
and the model picker.

## Responsive and accessibility behavior

- Authentication options wrap to a two-column grid and one column at narrow
widths.
- Text labels remain visible; meaning never depends on color or an icon.
- Buttons retain visible focus rings and at least the existing JCode target
height.
- Copy and browser-open actions have accessible names.
- Pending uses `role=status`; failures use `role=alert`.
- Motion is limited to the existing spinner and disclosure transition, and
respects reduced-motion behavior inherited from the app.

## Visual tokens

Reuse `INPUT`, `BTN_PRIMARY`, `BTN_SECONDARY`, `BTN_DANGER`, `ROW`, `LABEL`,
`CHIP` and `Segmented` from the Settings atoms. Use only existing CSS custom
properties and Heroicons (`KeyIcon`, `UserCircleIcon`, `ArrowPathIcon`,
`ClipboardDocumentIcon`, `ArrowTopRightOnSquareIcon`,
`ExclamationTriangleIcon`). Provider branding continues through
`ProviderIcon`; no hand-written SVG or hard-coded status color is added.
Loading
Loading