docs: update AI copilot spec — OAuth PKCE auth against Max subscription (no API key)
This commit is contained in:
parent
1962d2c9bc
commit
1793576030
@ -77,13 +77,85 @@ This is NOT a chatbot sidebar. It's a second operator with the same access as th
|
||||
|
||||
## 3. AI Service Layer (`internal/ai/`)
|
||||
|
||||
### 3.1 Claude API Client
|
||||
### 3.1 Authentication — OAuth PKCE (Max Subscription)
|
||||
|
||||
Wraith authenticates against the user's Claude Max subscription via OAuth Authorization Code Flow with PKCE. No API key needed. No per-token billing. Same auth path as Claude Code, but with Wraith's own independent token set (no shared credential file, no race conditions).
|
||||
|
||||
**OAuth Parameters:**
|
||||
|
||||
| Parameter | Value |
|
||||
|---|---|
|
||||
| Authorize URL | `https://claude.ai/oauth/authorize` |
|
||||
| Token URL | `https://platform.claude.com/v1/oauth/token` |
|
||||
| Client ID | `9d1c250a-e61b-44d9-88ed-5944d1962f5e` |
|
||||
| PKCE Method | S256 |
|
||||
| Code Verifier | 32 random bytes, base64url (no padding) |
|
||||
| Code Challenge | SHA-256(verifier), base64url (no padding) |
|
||||
| Redirect URI | `http://localhost:{dynamic_port}/callback` |
|
||||
| Scopes | `user:inference user:profile` |
|
||||
| State | 32 random bytes, base64url |
|
||||
|
||||
**Auth Flow:**
|
||||
|
||||
```
|
||||
1. User clicks "Connect to Claude" in Wraith copilot settings
|
||||
2. Wraith generates PKCE code_verifier + code_challenge
|
||||
3. Wraith starts a local HTTP server on a random port
|
||||
4. Wraith opens browser to:
|
||||
https://claude.ai/oauth/authorize
|
||||
?code=true
|
||||
&client_id=9d1c250a-e61b-44d9-88ed-5944d1962f5e
|
||||
&response_type=code
|
||||
&redirect_uri=http://localhost:{port}/callback
|
||||
&scope=user:inference user:profile
|
||||
&code_challenge={challenge}
|
||||
&code_challenge_method=S256
|
||||
&state={state}
|
||||
5. User logs in with their Anthropic/Claude account
|
||||
6. Browser redirects to http://localhost:{port}/callback?code={auth_code}&state={state}
|
||||
7. Wraith validates state, exchanges code for tokens:
|
||||
POST https://platform.claude.com/v1/oauth/token
|
||||
{
|
||||
"grant_type": "authorization_code",
|
||||
"code": "{auth_code}",
|
||||
"redirect_uri": "http://localhost:{port}/callback",
|
||||
"client_id": "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
|
||||
"code_verifier": "{verifier}",
|
||||
"state": "{state}"
|
||||
}
|
||||
8. Response: { access_token, refresh_token, expires_in, scope }
|
||||
9. Wraith encrypts tokens with vault and stores in SQLite settings:
|
||||
- ai_access_token (vault-encrypted)
|
||||
- ai_refresh_token (vault-encrypted)
|
||||
- ai_token_expires_at (unix timestamp)
|
||||
10. Done — copilot is authenticated
|
||||
```
|
||||
|
||||
**Token Refresh (automatic, silent):**
|
||||
|
||||
```
|
||||
When access_token is expired (checked before each API call):
|
||||
POST https://platform.claude.com/v1/oauth/token
|
||||
{
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": "{decrypted_refresh_token}",
|
||||
"client_id": "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
|
||||
"scope": "user:inference user:profile"
|
||||
}
|
||||
→ New access_token + refresh_token stored in vault
|
||||
```
|
||||
|
||||
**Implementation:** `internal/ai/oauth.go` — Go HTTP server for callback, PKCE helpers, token exchange, token refresh. Uses `pkg/browser` to open the authorize URL.
|
||||
|
||||
**Fallback:** For users without a Max subscription, allow raw API key input (stored in vault). The client checks which auth method is configured and uses the appropriate header.
|
||||
|
||||
### 3.2 Claude API Client
|
||||
|
||||
Direct HTTP client — no Python sidecar, no external SDK. Pure Go.
|
||||
|
||||
```go
|
||||
type ClaudeClient struct {
|
||||
apiKey string // decrypted from vault on demand
|
||||
auth *OAuthManager // handles token refresh + auth header
|
||||
model string // configurable: claude-sonnet-4-5-20250514, etc.
|
||||
httpClient *http.Client
|
||||
baseURL string // https://api.anthropic.com
|
||||
@ -91,9 +163,11 @@ type ClaudeClient struct {
|
||||
|
||||
// SendMessage sends a messages API request with tool use + vision support.
|
||||
// Returns a streaming response channel for token-by-token delivery.
|
||||
func (c *ClaudeClient) SendMessage(req *MessageRequest) (<-chan StreamEvent, error)
|
||||
func (c *ClaudeClient) SendMessage(messages []Message, tools []Tool, systemPrompt string) (<-chan StreamEvent, error)
|
||||
```
|
||||
|
||||
**Auth header:** `Authorization: Bearer {access_token}` (from OAuth). Falls back to `x-api-key: {api_key}` if using raw API key auth.
|
||||
|
||||
**Message format:** Anthropic Messages API v1 (`/v1/messages`).
|
||||
|
||||
**Streaming:** SSE (`stream: true`). Parse `event: content_block_delta`, `event: content_block_stop`, `event: message_delta`, `event: tool_use` events. Emit to frontend via Wails events.
|
||||
@ -322,6 +396,8 @@ internal/
|
||||
ai/
|
||||
service.go # AIService — orchestrates everything
|
||||
service_test.go
|
||||
oauth.go # OAuth PKCE flow — authorize, callback, token exchange, refresh
|
||||
oauth_test.go
|
||||
client.go # ClaudeClient — HTTP + SSE to Anthropic API
|
||||
client_test.go
|
||||
tools.go # Tool definitions (JSON schema)
|
||||
@ -432,8 +508,12 @@ Track cumulative token usage per day/month. When approaching the configured budg
|
||||
|
||||
## 10. Security Considerations
|
||||
|
||||
- **API key** stored in vault (same AES-256-GCM encryption as SSH keys)
|
||||
- **API key never logged** — mask in all log output
|
||||
- **OAuth tokens** stored in vault (same AES-256-GCM encryption as SSH keys). Access token + refresh token both encrypted at rest.
|
||||
- **Tokens never logged** — mask in all log output. Only log token expiry times and auth status.
|
||||
- **Token refresh is automatic and silent** — no user interaction needed after initial login. Refresh token rotation handled properly (new refresh token replaces old).
|
||||
- **Independent from Claude Code** — Wraith has its own OAuth session. No shared credential files, no race conditions with other Anthropic apps.
|
||||
- **Fallback API key** also stored in vault if used instead of OAuth.
|
||||
- **Conversation content** may contain sensitive data (terminal output, file contents, screenshots of desktops). Stored in SQLite alongside other encrypted data. Consider encrypting the messages JSON blob with the vault key.
|
||||
- **Tool access is unrestricted** — the XO has the same access as the Commander. This is by design. The human is always watching and can take control.
|
||||
- **No autonomous session creation without Commander context** — the XO can open sessions, but the connections (with credentials) were set up by the Commander
|
||||
- **PKCE prevents token interception** — authorization code flow with S256 challenge ensures the code can only be exchanged by the app that initiated the flow
|
||||
|
||||
Loading…
Reference in New Issue
Block a user