@life-ai-tools/claude-code-sdk 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +186 -123
- package/dist/auth.d.ts +39 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,81 +1,144 @@
|
|
|
1
1
|
# opencode-claude-toolkit
|
|
2
2
|
|
|
3
|
-
Use your **Claude Max/Pro subscription**
|
|
4
|
-
|
|
5
|
-
This toolkit provides:
|
|
6
|
-
- **`@lifeaitools/claude-code-sdk`** — TypeScript SDK for the Claude Code API (streaming, tool use, conversation management)
|
|
7
|
-
- **`@lifeaitools/opencode-proxy`** — OpenAI-compatible proxy server that lets you use Claude Max/Pro in [opencode](https://github.com/opencode-ai/opencode), Cursor, or any OpenAI-compatible client
|
|
3
|
+
Use your **Claude Max/Pro subscription** in [opencode](https://github.com/opencode-ai/opencode) — no API key needed.
|
|
8
4
|
|
|
9
5
|
> **Why this exists?** Read our [Open Letter to Anthropic](OPEN-LETTER.md) about token efficiency, developer freedom, and collaboration.
|
|
10
6
|
|
|
11
7
|
---
|
|
12
8
|
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
2. **opencode** (or any OpenAI-compatible coding client):
|
|
23
|
-
```bash
|
|
24
|
-
# Via Go
|
|
25
|
-
go install github.com/opencode-ai/opencode@latest
|
|
26
|
-
# Or via npm
|
|
27
|
-
npm install -g opencode-ai
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
3. **Claude Code CLI** (needed once, to authenticate your subscription):
|
|
31
|
-
```bash
|
|
32
|
-
# Install Claude Code CLI
|
|
33
|
-
npm install -g @anthropic-ai/claude-code
|
|
34
|
-
|
|
35
|
-
# Log in (this creates ~/.claude/.credentials.json)
|
|
36
|
-
claude
|
|
37
|
-
# Follow the OAuth flow in your browser, then exit Claude CLI
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
4. **Verify credentials exist:**
|
|
41
|
-
```bash
|
|
42
|
-
ls ~/.claude/.credentials.json
|
|
43
|
-
# Should show the file — if not, run `claude` again
|
|
44
|
-
```
|
|
9
|
+
## Three Ways to Use
|
|
10
|
+
|
|
11
|
+
| Approach | Best for | Install |
|
|
12
|
+
|----------|----------|---------|
|
|
13
|
+
| **[Plugin](#-plugin-recommended)** | opencode users — native integration, no proxy | `opencode plugin @life-ai-tools/opencode-claude` |
|
|
14
|
+
| **[Proxy](#-proxy)** | Cursor, other OpenAI-compatible clients | `bunx @life-ai-tools/opencode-proxy` |
|
|
15
|
+
| **[SDK](#-sdk)** | Building your own tools | `npm i @life-ai-tools/claude-code-sdk` |
|
|
45
16
|
|
|
46
17
|
---
|
|
47
18
|
|
|
48
|
-
##
|
|
19
|
+
## 🔌 Plugin (Recommended)
|
|
20
|
+
|
|
21
|
+
The cleanest approach — installs directly into opencode as a native provider. No separate proxy, no configuration files. Each opencode instance manages its own credentials.
|
|
22
|
+
|
|
23
|
+
### Setup
|
|
49
24
|
|
|
50
25
|
```bash
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
26
|
+
# 1. Install the plugin
|
|
27
|
+
opencode plugin @life-ai-tools/opencode-claude
|
|
28
|
+
|
|
29
|
+
# 2. Login (opens browser)
|
|
30
|
+
opencode providers login -p claude-max
|
|
31
|
+
|
|
32
|
+
# 3. Done — Claude models appear in opencode's model list
|
|
33
|
+
opencode
|
|
54
34
|
```
|
|
55
35
|
|
|
56
|
-
|
|
36
|
+
That's it. Select **Claude Sonnet 4.6 (Max)**, **Opus 4.6 (Max)**, or **Haiku 4.5 (Max)** from the model picker. Costs show as $0 (subscription-included).
|
|
57
37
|
|
|
58
|
-
|
|
59
|
-
# Terminal 1: Start the proxy
|
|
60
|
-
bunx @lifeaitools/opencode-proxy --port 4040
|
|
38
|
+
### Per-Project Credentials
|
|
61
39
|
|
|
62
|
-
|
|
63
|
-
|
|
40
|
+
During login, choose where to save credentials:
|
|
41
|
+
|
|
42
|
+
- **"This project"** → saves to `./.claude/.credentials.json` — isolated to this project
|
|
43
|
+
- **"Global"** → saves to `~/.claude/.credentials.json` — shared across projects
|
|
44
|
+
|
|
45
|
+
This means you can use **different Anthropic accounts for different projects** simultaneously:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
Project A (personal account):
|
|
49
|
+
/projects/personal/.claude/.credentials.json
|
|
50
|
+
|
|
51
|
+
Project B (work account):
|
|
52
|
+
/projects/work/.claude/.credentials.json
|
|
53
|
+
|
|
54
|
+
Project C (shared/global):
|
|
55
|
+
~/.claude/.credentials.json
|
|
64
56
|
```
|
|
65
57
|
|
|
66
|
-
|
|
58
|
+
Each opencode instance loads credentials from its CWD, holds tokens in isolated closure memory, and refreshes independently. No interference between instances.
|
|
67
59
|
|
|
68
60
|
### Supported Models
|
|
69
61
|
|
|
70
|
-
| Model
|
|
71
|
-
|
|
72
|
-
| `claude-
|
|
73
|
-
| `claude-
|
|
74
|
-
| `claude-
|
|
62
|
+
| Model | Name in opencode | Best for |
|
|
63
|
+
|-------|-----------------|----------|
|
|
64
|
+
| `claude-sonnet-4-6-20250415` | Claude Sonnet 4.6 (Max) | Fast coding, daily driver |
|
|
65
|
+
| `claude-opus-4-6-20250415` | Claude Opus 4.6 (Max) | Complex reasoning, architecture |
|
|
66
|
+
| `claude-haiku-4-5-20251001` | Claude Haiku 4.5 (Max) | Quick tasks, low latency |
|
|
75
67
|
|
|
76
68
|
---
|
|
77
69
|
|
|
78
|
-
##
|
|
70
|
+
## 🔀 Proxy
|
|
71
|
+
|
|
72
|
+
For Cursor, Continue, or any OpenAI-compatible client. Runs a local server that translates between OpenAI format and Claude's API.
|
|
73
|
+
|
|
74
|
+
### Prerequisites
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Install Bun (runtime for the proxy)
|
|
78
|
+
curl -fsSL https://bun.sh/install | bash
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Quick Start
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Start proxy (runs as a background daemon)
|
|
85
|
+
bunx @life-ai-tools/opencode-proxy
|
|
86
|
+
|
|
87
|
+
# In opencode:
|
|
88
|
+
LOCAL_ENDPOINT=http://localhost:4040/v1 opencode
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Or use the launcher that starts proxy + opencode together:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
bunx @life-ai-tools/opencode-proxy launch
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### First-Time Login (no Claude CLI needed)
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Interactive login — opens browser
|
|
101
|
+
curl -X POST http://localhost:4040/admin/login
|
|
102
|
+
|
|
103
|
+
# Headless/remote — get URL to open manually
|
|
104
|
+
curl -X POST http://localhost:4040/admin/login-url
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Proxy Features
|
|
108
|
+
|
|
109
|
+
- **Daemon mode** — proxy survives opencode exit, shared by multiple instances
|
|
110
|
+
- **Zero-downtime reload** — `POST /admin/reload` drains active streams, starts new instance
|
|
111
|
+
- **Multi-account** — per-request credential routing via `X-Account` header
|
|
112
|
+
- **Active stream tracking** — `GET /health` shows `activeStreams`, `pid`, `uptime`
|
|
113
|
+
- **Error surfacing** — rate limits, auth errors shown in opencode (not swallowed)
|
|
114
|
+
- **Token usage logging** — `in/out/cache_read/cache_write/hit%` on every request
|
|
115
|
+
- **Verbose mode** — `--verbose` dumps raw SSE events for debugging
|
|
116
|
+
|
|
117
|
+
### Proxy Configuration
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# Custom port
|
|
121
|
+
bunx @life-ai-tools/opencode-proxy --port 8080
|
|
122
|
+
|
|
123
|
+
# Verbose logging
|
|
124
|
+
PROXY_VERBOSE=1 bunx @life-ai-tools/opencode-proxy
|
|
125
|
+
|
|
126
|
+
# Log to files
|
|
127
|
+
PROXY_LOG_DIR=/tmp/proxy-logs bunx @life-ai-tools/opencode-proxy
|
|
128
|
+
|
|
129
|
+
# Multiple accounts
|
|
130
|
+
bunx @life-ai-tools/opencode-proxy --accounts ~/.config/opencode-proxy/accounts.json
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Accounts file format:
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"work": "/home/user/.claude-work/.credentials.json",
|
|
137
|
+
"personal": "/home/user/.claude/.credentials.json"
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### opencode Configuration
|
|
79
142
|
|
|
80
143
|
To make opencode always use the proxy, add to your `.opencode.json`:
|
|
81
144
|
|
|
@@ -94,7 +157,7 @@ To make opencode always use the proxy, add to your `.opencode.json`:
|
|
|
94
157
|
}
|
|
95
158
|
```
|
|
96
159
|
|
|
97
|
-
|
|
160
|
+
Or set the environment variable permanently:
|
|
98
161
|
|
|
99
162
|
```bash
|
|
100
163
|
# Add to your .bashrc / .zshrc
|
|
@@ -103,21 +166,19 @@ export LOCAL_ENDPOINT=http://localhost:4040/v1
|
|
|
103
166
|
|
|
104
167
|
---
|
|
105
168
|
|
|
106
|
-
## SDK
|
|
169
|
+
## 📦 SDK
|
|
170
|
+
|
|
171
|
+
For building your own tools on top of Claude Max/Pro.
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
npm install @life-ai-tools/claude-code-sdk
|
|
175
|
+
```
|
|
107
176
|
|
|
108
177
|
```typescript
|
|
109
|
-
import { ClaudeCodeSDK } from '@
|
|
178
|
+
import { ClaudeCodeSDK } from '@life-ai-tools/claude-code-sdk'
|
|
110
179
|
|
|
111
180
|
const sdk = new ClaudeCodeSDK()
|
|
112
181
|
|
|
113
|
-
// Non-streaming
|
|
114
|
-
const response = await sdk.generate({
|
|
115
|
-
model: 'claude-sonnet-4-6-20250415',
|
|
116
|
-
messages: [{ role: 'user', content: 'Hello!' }],
|
|
117
|
-
maxTokens: 1024,
|
|
118
|
-
})
|
|
119
|
-
console.log(response.content)
|
|
120
|
-
|
|
121
182
|
// Streaming
|
|
122
183
|
for await (const event of sdk.stream({
|
|
123
184
|
model: 'claude-sonnet-4-6-20250415',
|
|
@@ -130,87 +191,81 @@ for await (const event of sdk.stream({
|
|
|
130
191
|
}
|
|
131
192
|
|
|
132
193
|
// Multi-turn conversation
|
|
133
|
-
import { Conversation } from '@
|
|
194
|
+
import { Conversation } from '@life-ai-tools/claude-code-sdk'
|
|
134
195
|
|
|
135
196
|
const conv = new Conversation(sdk, { model: 'claude-sonnet-4-6-20250415' })
|
|
136
197
|
const reply1 = await conv.send('What is TypeScript?')
|
|
137
198
|
const reply2 = await conv.send('How does it compare to JavaScript?')
|
|
199
|
+
|
|
200
|
+
// OAuth login (no Claude CLI needed)
|
|
201
|
+
import { oauthLogin } from '@life-ai-tools/claude-code-sdk'
|
|
202
|
+
|
|
203
|
+
const creds = await oauthLogin({
|
|
204
|
+
credentialsPath: './my-credentials.json',
|
|
205
|
+
})
|
|
138
206
|
```
|
|
139
207
|
|
|
140
208
|
See [`examples/`](examples/) for more usage patterns.
|
|
141
209
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
## Features
|
|
210
|
+
### SDK Features
|
|
145
211
|
|
|
146
|
-
- **Zero API key** — uses
|
|
212
|
+
- **Zero API key** — uses Claude Max/Pro OAuth credentials
|
|
147
213
|
- **Streaming** — real SSE streaming with text, thinking, and tool use events
|
|
148
|
-
- **Auto-refresh** — tokens
|
|
149
|
-
- **
|
|
150
|
-
- **
|
|
151
|
-
- **
|
|
152
|
-
- **
|
|
153
|
-
- **
|
|
154
|
-
- **
|
|
155
|
-
- **Client disconnect handling** — clean abort propagation from client to API
|
|
214
|
+
- **Auto-refresh** — tokens refreshed automatically when expired
|
|
215
|
+
- **OAuth login** — full PKCE flow, no Claude CLI dependency
|
|
216
|
+
- **Retry logic** — exponential backoff for 5xx errors
|
|
217
|
+
- **Tool use** — full function calling support
|
|
218
|
+
- **Thinking** — extended thinking / chain-of-thought
|
|
219
|
+
- **Prompt caching** — automatic cache markers (5-min TTL, server-side)
|
|
220
|
+
- **Conversation** — stateful multi-turn management
|
|
156
221
|
|
|
157
222
|
---
|
|
158
223
|
|
|
159
|
-
##
|
|
224
|
+
## How It Works
|
|
160
225
|
|
|
161
|
-
###
|
|
226
|
+
### Plugin (direct)
|
|
162
227
|
```
|
|
163
|
-
|
|
228
|
+
opencode ──→ plugin ──→ Anthropic API
|
|
229
|
+
│
|
|
230
|
+
├── OAuth token from closure memory
|
|
231
|
+
├── Auto-refresh on expiry
|
|
232
|
+
└── Per-project credential isolation
|
|
164
233
|
```
|
|
165
|
-
**Fix:** Run `claude` in your terminal, complete the OAuth login, then try again. The file `~/.claude/.credentials.json` must exist.
|
|
166
234
|
|
|
167
|
-
###
|
|
235
|
+
### Proxy (OpenAI-compatible)
|
|
168
236
|
```
|
|
169
|
-
|
|
237
|
+
┌─────────────┐ OpenAI format ┌──────────────────┐ Claude API ┌──────────────┐
|
|
238
|
+
│ opencode │ ──── SSE stream ──→ │ opencode-proxy │ ──── SSE ──────→ │ Anthropic │
|
|
239
|
+
│ Cursor │ ←── SSE stream ──── │ (daemon) │ ←── SSE ──────── │ API │
|
|
240
|
+
│ any client │ └──────────────────┘ └──────────────┘
|
|
241
|
+
└─────────────┘
|
|
170
242
|
```
|
|
171
|
-
**Fix:** You've hit your subscription's usage limit. Wait for the reset window (usually resets daily). The proxy never retries 429 errors — this is by design, as subscription rate limits are window-based.
|
|
172
243
|
|
|
173
|
-
|
|
174
|
-
If opencode shows a timeout while waiting for a response (especially with Opus):
|
|
175
|
-
- The proxy has a 255-second idle timeout and 600-second request timeout
|
|
176
|
-
- Opus can take 30+ seconds for complex reasoning
|
|
177
|
-
- If timeouts persist, check your network connection to `api.anthropic.com`
|
|
244
|
+
---
|
|
178
245
|
|
|
179
|
-
|
|
180
|
-
```bash
|
|
181
|
-
# Check if port 4040 is already in use
|
|
182
|
-
lsof -i :4040
|
|
246
|
+
## Troubleshooting
|
|
183
247
|
|
|
184
|
-
|
|
185
|
-
bunx @lifeaitools/opencode-proxy --port 4041
|
|
248
|
+
### "Not logged in"
|
|
186
249
|
```
|
|
187
|
-
|
|
188
|
-
### "Token expired" / 401 errors
|
|
189
|
-
The SDK auto-refreshes tokens. If you see persistent 401 errors:
|
|
190
|
-
```bash
|
|
191
|
-
# Re-authenticate
|
|
192
|
-
claude # log in again
|
|
193
|
-
# Restart the proxy
|
|
250
|
+
Run: opencode providers login -p claude-max
|
|
194
251
|
```
|
|
252
|
+
Or for proxy: `curl -X POST http://localhost:4040/admin/login`
|
|
195
253
|
|
|
196
|
-
|
|
254
|
+
### "Rate limited" / 429
|
|
255
|
+
You've hit your subscription's usage limit. Wait for the reset window (usually daily). Rate limits are never retried — this is by design for subscription-based rate limiting.
|
|
197
256
|
|
|
198
|
-
|
|
257
|
+
### "Token expired" / 401
|
|
258
|
+
Tokens auto-refresh. If persistent:
|
|
259
|
+
```bash
|
|
260
|
+
# Plugin: re-login
|
|
261
|
+
opencode providers login -p claude-max
|
|
199
262
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
│ opencode │ ──── SSE stream ──→ │ opencode-proxy │ ──── SSE ──────→ │ Anthropic │
|
|
203
|
-
│ (client) │ ←── SSE stream ──── │ (Bun.serve) │ ←── SSE ──────── │ API │
|
|
204
|
-
└─────────────┘ └──────────────────┘ └──────────────┘
|
|
205
|
-
│
|
|
206
|
-
claude-code-sdk
|
|
207
|
-
• OAuth auth + refresh
|
|
208
|
-
• Request building
|
|
209
|
-
• SSE parsing
|
|
210
|
-
• Retry logic
|
|
263
|
+
# Proxy: re-login
|
|
264
|
+
curl -X POST http://localhost:4040/admin/login
|
|
211
265
|
```
|
|
212
266
|
|
|
213
|
-
|
|
267
|
+
### Stream timeout (Opus)
|
|
268
|
+
Opus can take 30+ seconds for complex reasoning. The proxy has a 255-second idle timeout and 600-second request timeout. If timeouts persist, check your network to `api.anthropic.com`.
|
|
214
269
|
|
|
215
270
|
---
|
|
216
271
|
|
|
@@ -219,20 +274,28 @@ The proxy translates between OpenAI's chat completion format and Claude's native
|
|
|
219
274
|
```
|
|
220
275
|
opencode-claude-toolkit/
|
|
221
276
|
├── packages/
|
|
222
|
-
│
|
|
223
|
-
│
|
|
277
|
+
│ ├── opencode-plugin/ # opencode plugin (recommended)
|
|
278
|
+
│ │ └── src/index.ts # OAuth, token management, model config
|
|
279
|
+
│ └── opencode-proxy/ # OpenAI-compatible proxy server
|
|
280
|
+
│ ├── server.ts # HTTP server with daemon mode
|
|
224
281
|
│ ├── translate.ts # OpenAI ↔ Claude format translation
|
|
225
282
|
│ └── launch.ts # Auto-launcher
|
|
226
|
-
├── dist/ # Compiled SDK
|
|
283
|
+
├── dist/ # Compiled SDK
|
|
227
284
|
├── examples/ # Usage examples
|
|
228
|
-
│ ├── basic-chat.ts
|
|
229
|
-
│ └── conversation.ts
|
|
230
285
|
├── OPEN-LETTER.md # Our message to Anthropic
|
|
231
|
-
├── REQUEST-SOURCE.md #
|
|
286
|
+
├── REQUEST-SOURCE.md # SDK source access requests
|
|
232
287
|
├── LICENSE # MIT
|
|
233
288
|
└── README.md
|
|
234
289
|
```
|
|
235
290
|
|
|
291
|
+
## npm Packages
|
|
292
|
+
|
|
293
|
+
| Package | Version | Description |
|
|
294
|
+
|---------|---------|-------------|
|
|
295
|
+
| [`@life-ai-tools/opencode-claude`](https://www.npmjs.com/package/@life-ai-tools/opencode-claude) | 0.1.1 | opencode plugin — native Claude Max/Pro |
|
|
296
|
+
| [`@life-ai-tools/opencode-proxy`](https://www.npmjs.com/package/@life-ai-tools/opencode-proxy) | 0.3.1 | OpenAI-compatible proxy server |
|
|
297
|
+
| [`@life-ai-tools/claude-code-sdk`](https://www.npmjs.com/package/@life-ai-tools/claude-code-sdk) | 0.1.1 | TypeScript SDK |
|
|
298
|
+
|
|
236
299
|
---
|
|
237
300
|
|
|
238
301
|
## License
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 Authorization Code + PKCE flow for Claude.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the Claude Code CLI OAuth implementation:
|
|
5
|
+
* - Generates PKCE code_verifier + challenge (S256)
|
|
6
|
+
* - Opens browser to Anthropic's auth page
|
|
7
|
+
* - Listens on localhost for callback with auth code
|
|
8
|
+
* - Exchanges code for access/refresh tokens
|
|
9
|
+
* - Saves credentials to .credentials.json
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* const creds = await oauthLogin({ credentialsPath: '~/.claude/.credentials.json' })
|
|
13
|
+
*/
|
|
14
|
+
export interface OAuthLoginOptions {
|
|
15
|
+
/** Where to save credentials. Default: ~/.claude/.credentials.json */
|
|
16
|
+
credentialsPath?: string;
|
|
17
|
+
/** Port for localhost callback. Default: 0 (OS-assigned) */
|
|
18
|
+
port?: number;
|
|
19
|
+
/** Callback when the auth URL is ready — display to user. If not provided, prints to stdout. */
|
|
20
|
+
onAuthUrl?: (url: string, manualUrl: string) => void;
|
|
21
|
+
/** Try to open browser automatically. Default: true */
|
|
22
|
+
openBrowser?: boolean;
|
|
23
|
+
/** Prefer Claude.ai personal login route (better for Pro/Max users). Default: true */
|
|
24
|
+
loginWithClaudeAi?: boolean;
|
|
25
|
+
/** Optional login hint (email) */
|
|
26
|
+
loginHint?: string;
|
|
27
|
+
/** Optional login method hint (e.g. sso, google, magic_link) */
|
|
28
|
+
loginMethod?: string;
|
|
29
|
+
/** Optional organization UUID for enterprise flows */
|
|
30
|
+
orgUUID?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface OAuthResult {
|
|
33
|
+
accessToken: string;
|
|
34
|
+
refreshToken: string;
|
|
35
|
+
expiresAt: number;
|
|
36
|
+
credentialsPath: string;
|
|
37
|
+
}
|
|
38
|
+
export declare function oauthLogin(options?: OAuthLoginOptions): Promise<OAuthResult>;
|
|
39
|
+
//# sourceMappingURL=auth.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -4,4 +4,6 @@ export { saveSession, loadSession } from './session.js';
|
|
|
4
4
|
export type { ClaudeCodeSDKOptions, GenerateOptions, GenerateResponse, StreamEvent, TokenUsage, RateLimitInfo, CredentialsFile, MessageParam, ContentBlockParam, TextBlockParam, ToolDef, ToolChoice, SystemParam, ContentBlock, TextBlock, ThinkingBlock, ToolUseBlock, ConversationOptions, TurnOptions, CredentialStore, StoredCredentials, } from './types.js';
|
|
5
5
|
export { ClaudeCodeSDKError, AuthError, APIError, RateLimitError, } from './types.js';
|
|
6
6
|
export type { SessionEntry } from './session.js';
|
|
7
|
+
export { oauthLogin } from './auth.js';
|
|
8
|
+
export type { OAuthLoginOptions, OAuthResult } from './auth.js';
|
|
7
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* (c) 2026 Kiberos. Compiled distribution.
|
|
4
4
|
* Source access: see REQUEST-SOURCE.md in the GitHub repo.
|
|
5
5
|
*/
|
|
6
|
-
var t=Object.defineProperty,e=(e,s)=>t(e,"name",{value:s,configurable:!0});import{createHash as s,randomBytes as i,randomUUID as r}from"crypto";import{readFileSync as n,writeFileSync as o,chmodSync as a,mkdirSync as l,statSync as h}from"fs";import{join as u}from"path";import{homedir as c}from"os";var d=class extends Error{constructor(t,e){super(t),this.cause=e,this.name="ClaudeCodeSDKError"}static{e(this,"ClaudeCodeSDKError")}},p=class extends d{static{e(this,"AuthError")}constructor(t,e){super(t,e),this.name="AuthError"}},f=class extends d{constructor(t,e,s,i){super(t,i),this.status=e,this.requestId=s,this.name="APIError"}static{e(this,"APIError")}},y=class extends d{constructor(t,e,s=429,i){super(t,i),this.rateLimitInfo=e,this.status=s,this.name="RateLimitError"}static{e(this,"RateLimitError")}},m=3e5,_=class{static{e(this,"ClaudeCodeSDK")}accessToken=null;refreshToken=null;expiresAt=null;credentialStore;sessionId;deviceId;accountUuid;version;timeout;maxRetries;lastRateLimitInfo={status:null,resetAt:null,claim:null,retryAfter:null};pending401=null;lastFailedToken=null;constructor(t={}){this.sessionId=r(),this.deviceId=t.deviceId??i(32).toString("hex"),this.accountUuid=t.accountUuid??this.readAccountUuid(),this.version=t.version??"0.1.0",this.timeout=t.timeout??6e5,this.maxRetries=t.maxRetries??3,t.credentialStore?this.credentialStore=t.credentialStore:t.accessToken?(this.accessToken=t.accessToken,this.refreshToken=t.refreshToken??null,this.expiresAt=t.expiresAt??null,this.credentialStore=new w({accessToken:t.accessToken,refreshToken:t.refreshToken??"",expiresAt:t.expiresAt??0})):this.credentialStore=new k(t.credentialsPath??u(c(),".claude",".credentials.json"))}async generate(t){let e=[];for await(let s of this.stream(t))e.push(s);return this.assembleResponse(e,t.model)}async*stream(t){await this.ensureAuth();let e,s=this.buildRequestBody(t),i=this.buildHeaders(t);for(let r=1;r<=this.maxRetries+1;r++){if(t.signal?.aborted)throw new d("Aborted");try{return void(yield*this.doStreamRequest(s,i,t.signal))}catch(s){if(e=s,s instanceof f){if(401===s.status&&r<=this.maxRetries){await this.handleAuth401(),i.Authorization=`Bearer ${this.accessToken}`;continue}if(429===s.status)throw s instanceof y?s:new y("Rate limited",this.lastRateLimitInfo,429,s);if(s.status>=500&&r<=this.maxRetries){let e=this.getRetryDelay(r,this.lastRateLimitInfo.retryAfter?.toString()??null);await this.sleep(e,t.signal);continue}}throw s}}throw e}getRateLimitInfo(){return this.lastRateLimitInfo}async*doStreamRequest(t,e,s){let i,r=new AbortController,n=setTimeout(()=>r.abort(),this.timeout);s&&s.addEventListener("abort",()=>r.abort(),{once:!0});try{i=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:e,body:JSON.stringify(t),signal:r.signal})}catch(t){throw clearTimeout(n),new d("Network error",t)}if(clearTimeout(n),this.lastRateLimitInfo=this.parseRateLimitHeaders(i.headers),!i.ok){let t="";try{t=await i.text()}catch{}let e=i.headers.get("request-id");throw 429===i.status?new y(`Rate limited: ${t}`,this.lastRateLimitInfo,429):new f(`API error ${i.status}: ${t}`,i.status,e)}if(!i.body)throw new d("No response body");yield*this.parseSSE(i.body,s)}async*parseSSE(t,e){let s=new TextDecoder,i=t.getReader(),r="",n=new Map,o={inputTokens:0,outputTokens:0},a=null;try{for(;;){if(e?.aborted)return void i.cancel();let{done:t,value:l}=await i.read();if(t)break;r+=s.decode(l,{stream:!0});let h=r.split("\n");r=h.pop()??"";for(let t of h){if(!t.startsWith("data: "))continue;let e,s=t.slice(6);if("[DONE]"===s)continue;try{e=JSON.parse(s)}catch{continue}let i=e.type;if("message_start"===i){let t=e.message?.usage;t&&(o={inputTokens:t.input_tokens??0,outputTokens:t.output_tokens??0,cacheCreationInputTokens:t.cache_creation_input_tokens,cacheReadInputTokens:t.cache_read_input_tokens});continue}if("content_block_start"===i){let t=e.index,s=e.content_block;"tool_use"===s.type?(n.set(t,{type:"tool_use",id:s.id,name:s.name,input:""}),yield{type:"tool_use_start",id:s.id,name:s.name}):"text"===s.type?n.set(t,{type:"text",text:""}):"thinking"===s.type&&n.set(t,{type:"thinking",thinking:""});continue}if("content_block_delta"===i){let t=e.index,s=n.get(t),i=e.delta;"text_delta"===i.type&&void 0!==i.text?(s&&(s.text=(s.text??"")+i.text),i.text&&(yield{type:"text_delta",text:i.text})):"thinking_delta"===i.type&&void 0!==i.thinking?(s&&(s.thinking=(s.thinking??"")+i.thinking),i.thinking&&(yield{type:"thinking_delta",text:i.thinking})):"input_json_delta"===i.type&&void 0!==i.partial_json&&(s&&(s.input=(s.input??"")+i.partial_json),i.partial_json&&(yield{type:"tool_use_delta",partialInput:i.partial_json}));continue}if("content_block_stop"===i){let t=e.index,s=n.get(t);if("tool_use"===s?.type&&s.id&&s.name){let t={};try{t=JSON.parse(s.input??"{}")}catch{}yield{type:"tool_use_end",id:s.id,name:s.name,input:t}}continue}if("message_delta"===i){let t=e.delta;t?.stop_reason&&(a=t.stop_reason);let s=e.usage;s?.output_tokens&&(o={...o,outputTokens:s.output_tokens});continue}"message_stop"===i&&(yield{type:"message_stop",usage:o,stopReason:a})}}}finally{i.releaseLock()}}buildHeaders(t){let e=this.buildBetas(t);return{"Content-Type":"application/json",Authorization:`Bearer ${this.accessToken}`,"anthropic-version":"2023-06-01","anthropic-beta":e.join(","),"x-app":"cli","User-Agent":`claude-code/${this.version} (external, sdk)`,"X-Claude-Code-Session-Id":this.sessionId}}buildRequestBody(t){let e,s=this.computeFingerprint(t.messages),i=`x-anthropic-billing-header: cc_version=${this.version}.${s}; cc_entrypoint=cli;`;e="string"==typeof t.system?i+"\n"+t.system:Array.isArray(t.system)?[{type:"text",text:i},...t.system]:i;let r={model:t.model,messages:t.messages,max_tokens:t.maxTokens??16384,stream:!0,system:e,metadata:{user_id:JSON.stringify({device_id:this.deviceId,account_uuid:this.accountUuid,session_id:this.sessionId})}};t.tools&&t.tools.length>0&&(r.tools=t.tools,t.toolChoice&&(r.tool_choice="string"==typeof t.toolChoice?{type:t.toolChoice}:t.toolChoice)),!1!==t.caching&&this.addCacheMarkers(r);let n=t.model.toLowerCase(),o=n.includes("opus-4-6")||n.includes("sonnet-4-6"),a="disabled"===t.thinking?.type;return!a&&o?r.thinking={type:"adaptive"}:"enabled"===t.thinking?.type&&(r.thinking={type:"enabled",budget_tokens:t.thinking.budgetTokens}),!(!a&&(o||"enabled"===t.thinking?.type))&&void 0!==t.temperature&&(r.temperature=t.temperature),void 0!==t.topP&&(r.top_p=t.topP),t.effort&&o&&(r.output_config={effort:t.effort}),t.stopSequences?.length&&(r.stop_sequences=t.stopSequences),t.fast&&(r.speed="fast"),r}addCacheMarkers(t){let e=t.system;if("string"==typeof e)t.system=[{type:"text",text:e,cache_control:{type:"ephemeral"}}];else if(Array.isArray(e)){let t=e;t.length>0&&(t[t.length-1]={...t[t.length-1],cache_control:{type:"ephemeral"}})}let s=t.messages;if(0===s.length)return;let i=s[s.length-1];if("string"==typeof i.content)i.content=[{type:"text",text:i.content,cache_control:{type:"ephemeral"}}];else if(Array.isArray(i.content)&&i.content.length>0){let t=i.content[i.content.length-1];i.content[i.content.length-1]={...t,cache_control:{type:"ephemeral"}}}}buildBetas(t){let e=[],s=t.model.toLowerCase().includes("haiku");return s||e.push("claude-code-20250219"),e.push("oauth-2025-04-20"),/\[1m\]/i.test(t.model)&&e.push("context-1m-2025-08-07"),!s&&"disabled"!==t.thinking?.type&&e.push("interleaved-thinking-2025-05-14"),t.effort&&e.push("effort-2025-11-24"),t.fast&&e.push("fast-mode-2026-02-01"),e.push("prompt-caching-scope-2026-01-05"),t.extraBetas&&e.push(...t.extraBetas),e}async ensureAuth(){this.accessToken&&!this.isTokenExpired()||this.credentialStore.hasChanged&&await this.credentialStore.hasChanged()&&(await this.loadFromStore(),this.accessToken&&!this.isTokenExpired())||!this.accessToken&&(await this.loadFromStore(),this.accessToken&&!this.isTokenExpired())||this.accessToken&&this.isTokenExpired()&&await this.refreshTokenWithTripleCheck()}async loadFromStore(){let t=await this.credentialStore.read();if(!t?.accessToken)throw new p('No OAuth tokens found. Run "claude login" first or provide credentials.');this.accessToken=t.accessToken,this.refreshToken=t.refreshToken,this.expiresAt=t.expiresAt}isTokenExpired(){return!!this.expiresAt&&Date.now()+m>=this.expiresAt}async refreshTokenWithTripleCheck(){let t=await this.credentialStore.read();if(t&&!(Date.now()+m>=t.expiresAt))return this.accessToken=t.accessToken,this.refreshToken=t.refreshToken,void(this.expiresAt=t.expiresAt);await this.doTokenRefresh()}async handleAuth401(){let t=this.accessToken;this.pending401&&this.lastFailedToken===t||(this.lastFailedToken=t,this.pending401=(async()=>{let e=await this.credentialStore.read();return e&&e.accessToken!==t?(this.accessToken=e.accessToken,this.refreshToken=e.refreshToken,this.expiresAt=e.expiresAt,!0):(await this.doTokenRefresh(),!0)})().finally(()=>{this.pending401=null,this.lastFailedToken=null})),await this.pending401}async doTokenRefresh(){if(!this.refreshToken)throw new p("Token expired and no refresh token available.");let t=await fetch("https://platform.claude.com/v1/oauth/token",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({grant_type:"refresh_token",refresh_token:this.refreshToken,client_id:"9d1c250a-e61b-44d9-88ed-5944d1962f5e"}),signal:AbortSignal.timeout(15e3)});if(!t.ok){let e=await this.credentialStore.read();if(e&&!(Date.now()+m>=e.expiresAt))return this.accessToken=e.accessToken,this.refreshToken=e.refreshToken,void(this.expiresAt=e.expiresAt);throw new p(`Token refresh failed: ${t.status} ${t.statusText}`)}let e=await t.json();this.accessToken=e.access_token,this.refreshToken=e.refresh_token??this.refreshToken,this.expiresAt=Date.now()+1e3*e.expires_in,await this.credentialStore.write({accessToken:this.accessToken,refreshToken:this.refreshToken,expiresAt:this.expiresAt})}computeFingerprint(t){let e=t.find(t=>"user"===t.role);if(!e)return this.hashFingerprint("000");let s="";"string"==typeof e.content?s=e.content:Array.isArray(e.content)&&(s=e.content.find(t=>"text"===t.type)?.text??"");let i=[4,7,20].map(t=>s[t]||"0").join("");return this.hashFingerprint(i)}hashFingerprint(t){let e=`59cf53e54c78${t}${this.version}`;return s("sha256").update(e).digest("hex").slice(0,3)}assembleResponse(t,e){let s=[],i=[],r=[],n={inputTokens:0,outputTokens:0},o=null,a="",l="";for(let e of t)switch(e.type){case"text_delta":a+=e.text;break;case"thinking_delta":l+=e.text;break;case"tool_use_end":r.push({type:"tool_use",id:e.id,name:e.name,input:e.input});break;case"message_stop":n=e.usage,o=e.stopReason;break;case"error":throw e.error}return a&&s.push({type:"text",text:a}),l&&i.push({type:"thinking",thinking:l}),s.push(...r),{content:s,thinking:i.length>0?i:void 0,toolCalls:r.length>0?r:void 0,usage:n,stopReason:o,rateLimitInfo:this.lastRateLimitInfo,model:e}}parseRateLimitHeaders(t){let e=t.get("retry-after"),s=t.get("anthropic-ratelimit-unified-reset"),i=s?Number(s):null;return{status:t.get("anthropic-ratelimit-unified-status"),resetAt:Number.isFinite(i)?i:null,claim:t.get("anthropic-ratelimit-unified-representative-claim"),retryAfter:e?parseFloat(e):null}}getRetryDelay(t,e){if(e){let t=parseInt(e,10);if(!isNaN(t))return 1e3*t}let s=Math.min(500*Math.pow(2,t-1),32e3);return s+.25*Math.random()*s}sleep(t,e){return new Promise((s,i)=>{if(e?.aborted)return void i(new d("Aborted"));let r=setTimeout(s,t);e?.addEventListener("abort",()=>{clearTimeout(r),i(new d("Aborted"))},{once:!0})})}readAccountUuid(){try{let t=u(c(),".claude","claude_code_config.json");return JSON.parse(n(t,"utf8")).oauthAccount?.accountUuid??""}catch{return""}}},k=class{constructor(t){this.path=t}static{e(this,"FileCredentialStore")}lastMtimeMs=0;async read(){try{let t=n(this.path,"utf8");return this.lastMtimeMs=this.getMtime(),JSON.parse(t).claudeAiOauth??null}catch{return null}}async write(t){let e={};try{e=JSON.parse(n(this.path,"utf8"))}catch{}e.claudeAiOauth=t;let s=u(this.path,"..");try{l(s,{recursive:!0})}catch{}o(this.path,JSON.stringify(e,null,2),"utf8"),a(this.path,384),this.lastMtimeMs=this.getMtime()}async hasChanged(){let t=this.getMtime();return t!==this.lastMtimeMs&&(this.lastMtimeMs=t,!0)}getMtime(){try{return h(this.path).mtimeMs}catch{return 0}}},w=class{static{e(this,"MemoryCredentialStore")}credentials;constructor(t){this.credentials={...t}}async read(){return this.credentials.accessToken?{...this.credentials}:null}async write(t){this.credentials={...t}}},g=class _Conversation{static{e(this,"Conversation")}sdk;options;t=[];i={inputTokens:0,outputTokens:0};constructor(t,e){this.sdk=t,this.options=e}get messages(){return this.t}get totalUsage(){return{...this.i}}get length(){return this.t.length}async send(t,e){this.appendUserMessage(t);let s=this.buildGenerateOptions(e),i=await this.sdk.generate(s);return this.appendAssistantFromResponse(i),this.accumulateUsage(i.usage),i}async*stream(t,e){this.appendUserMessage(t);let s=this.buildGenerateOptions(e),i=[],r=[],n=[],o={inputTokens:0,outputTokens:0};for await(let t of this.sdk.stream(s))switch(yield t,t.type){case"text_delta":i.push(t.text);break;case"thinking_delta":r.push(t.text);break;case"tool_use_end":n.push({type:"tool_use",id:t.id,name:t.name,input:t.input});break;case"message_stop":o=t.usage}let a=[];i.length>0&&a.push({type:"text",text:i.join("")});for(let t of n)a.push({type:"tool_use",id:t.id,name:t.name,input:t.input});a.length>0&&this.t.push({role:"assistant",content:a}),this.accumulateUsage(o)}addToolResult(t,e,s){let i={type:"tool_result",tool_use_id:t,content:e,...s&&{is_error:!0}};this.t.push({role:"user",content:[i]})}addToolResults(t){let e=t.map(t=>({type:"tool_result",tool_use_id:t.toolUseId,content:t.content,...t.isError&&{is_error:!0}}));this.t.push({role:"user",content:e})}async continue(t){let e=this.buildGenerateOptions(t),s=await this.sdk.generate(e);return this.appendAssistantFromResponse(s),this.accumulateUsage(s.usage),s}async*continueStream(t){let e=this.buildGenerateOptions(t),s=[],i=[],r={inputTokens:0,outputTokens:0};for await(let t of this.sdk.stream(e))switch(yield t,t.type){case"text_delta":s.push(t.text);break;case"tool_use_end":i.push({type:"tool_use",id:t.id,name:t.name,input:t.input});break;case"message_stop":r=t.usage}let n=[];s.length>0&&n.push({type:"text",text:s.join("")});for(let t of i)n.push({type:"tool_use",id:t.id,name:t.name,input:t.input});n.length>0&&this.t.push({role:"assistant",content:n}),this.accumulateUsage(r)}rewind(t){if(t<0||t>=this.t.length)throw new Error(`Invalid rewind index: ${t}`);return this.t.splice(t)}undoLastTurn(){for(let t=this.t.length-1;t>=0;t--){let e=this.t[t];if("user"===e.role){let s=e.content;if(!(Array.isArray(s)&&s.length>0&&"tool_result"===s[0].type))return this.rewind(t)}}return[]}branch(){let t=new _Conversation(this.sdk,{...this.options});return t.t=[...this.t],t.i={...this.i},t}getHistory(){return this.t.map((t,e)=>{let s="";if("string"==typeof t.content)s=t.content.slice(0,100);else if(Array.isArray(t.content)){let e=t.content[0];"text"===e?.type?s=e.text?.slice(0,100)??"":"tool_result"===e?.type?s=`[tool_result: ${e.tool_use_id}]`:"tool_use"===e?.type&&(s=`[tool_use: ${e.name}]`)}return{index:e,role:t.role,preview:s}})}appendUserMessage(t){this.t.push({role:"user",content:t})}appendAssistantFromResponse(t){let e=[];for(let s of t.content)"text"===s.type?e.push({type:"text",text:s.text}):"tool_use"===s.type&&e.push({type:"tool_use",id:s.id,name:s.name,input:s.input});e.length>0&&this.t.push({role:"assistant",content:e})}buildGenerateOptions(t){return{model:this.options.model,messages:[...this.t],system:this.options.system,tools:t?.tools??this.options.tools,toolChoice:t?.toolChoice??this.options.toolChoice,maxTokens:this.options.maxTokens,thinking:this.options.thinking,effort:this.options.effort,fast:this.options.fast,signal:t?.signal??this.options.signal,extraBetas:this.options.extraBetas,caching:this.options.caching}}accumulateUsage(t){this.i.inputTokens+=t.inputTokens,this.i.outputTokens+=t.outputTokens,this.i.cacheCreationInputTokens=(this.i.cacheCreationInputTokens??0)+(t.cacheCreationInputTokens??0),this.i.cacheReadInputTokens=(this.i.cacheReadInputTokens??0)+(t.cacheReadInputTokens??0)}};import{readFileSync as x,writeFileSync as b,mkdirSync as T}from"fs";import{dirname as v}from"path";import{randomUUID as S}from"crypto";function A(t,e){T(v(t),{recursive:!0});let s=null,i=[];for(let t of e){let e=S(),r={type:"user"===t.role?"user":"assistant",uuid:e,parentUuid:s,timestamp:Date.now(),content:t.content};i.push(JSON.stringify(r)),s=e}b(t,i.join("\n")+"\n","utf8")}function C(t){let e=x(t,"utf8"),s=[];for(let t of e.split("\n")){if(!t.trim())continue;let e;try{e=JSON.parse(t)}catch{continue}("user"===e.type||"assistant"===e.type)&&s.push({role:"user"===e.type?"user":"assistant",content:e.content})}return s}e(A,"saveSession"),e(C,"loadSession");export{f as APIError,p as AuthError,_ as ClaudeCodeSDK,d as ClaudeCodeSDKError,g as Conversation,k as FileCredentialStore,w as MemoryCredentialStore,y as RateLimitError,C as loadSession,A as saveSession};
|
|
6
|
+
var t=Object.defineProperty,e=(e,s)=>t(e,"name",{value:s,configurable:!0});import{createHash as s,randomBytes as i,randomUUID as r}from"crypto";import{readFileSync as n,writeFileSync as o,chmodSync as a,mkdirSync as l,statSync as h}from"fs";import{join as c}from"path";import{homedir as u}from"os";var d=class extends Error{constructor(t,e){super(t),this.cause=e,this.name="ClaudeCodeSDKError"}static{e(this,"ClaudeCodeSDKError")}},p=class extends d{static{e(this,"AuthError")}constructor(t,e){super(t,e),this.name="AuthError"}},f=class extends d{constructor(t,e,s,i){super(t,i),this.status=e,this.requestId=s,this.name="APIError"}static{e(this,"APIError")}},m=class extends d{constructor(t,e,s=429,i){super(t,i),this.rateLimitInfo=e,this.status=s,this.name="RateLimitError"}static{e(this,"RateLimitError")}},y=3e5,_=class{static{e(this,"ClaudeCodeSDK")}accessToken=null;refreshToken=null;expiresAt=null;credentialStore;sessionId;deviceId;accountUuid;version;timeout;maxRetries;lastRateLimitInfo={status:null,resetAt:null,claim:null,retryAfter:null};pending401=null;lastFailedToken=null;constructor(t={}){this.sessionId=r(),this.deviceId=t.deviceId??i(32).toString("hex"),this.accountUuid=t.accountUuid??this.readAccountUuid(),this.version=t.version??"0.1.0",this.timeout=t.timeout??6e5,this.maxRetries=t.maxRetries??3,t.credentialStore?this.credentialStore=t.credentialStore:t.accessToken?(this.accessToken=t.accessToken,this.refreshToken=t.refreshToken??null,this.expiresAt=t.expiresAt??null,this.credentialStore=new g({accessToken:t.accessToken,refreshToken:t.refreshToken??"",expiresAt:t.expiresAt??0})):this.credentialStore=new w(t.credentialsPath??c(u(),".claude",".credentials.json"))}async generate(t){let e=[];for await(let s of this.stream(t))e.push(s);return this.assembleResponse(e,t.model)}async*stream(t){await this.ensureAuth();let e,s=this.buildRequestBody(t),i=this.buildHeaders(t);for(let r=1;r<=this.maxRetries+1;r++){if(t.signal?.aborted)throw new d("Aborted");try{return void(yield*this.doStreamRequest(s,i,t.signal))}catch(s){if(e=s,s instanceof f){if(401===s.status&&r<=this.maxRetries){await this.handleAuth401(),i.Authorization=`Bearer ${this.accessToken}`;continue}if(429===s.status)throw s instanceof m?s:new m("Rate limited",this.lastRateLimitInfo,429,s);if(s.status>=500&&r<=this.maxRetries){let e=this.getRetryDelay(r,this.lastRateLimitInfo.retryAfter?.toString()??null);await this.sleep(e,t.signal);continue}}throw s}}throw e}getRateLimitInfo(){return this.lastRateLimitInfo}async*doStreamRequest(t,e,s){let i,r=new AbortController,n=setTimeout(()=>r.abort(),this.timeout);s&&s.addEventListener("abort",()=>r.abort(),{once:!0});try{i=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:e,body:JSON.stringify(t),signal:r.signal})}catch(t){throw clearTimeout(n),new d("Network error",t)}if(clearTimeout(n),this.lastRateLimitInfo=this.parseRateLimitHeaders(i.headers),!i.ok){let t="";try{t=await i.text()}catch{}let e=i.headers.get("request-id");throw 429===i.status?new m(`Rate limited: ${t}`,this.lastRateLimitInfo,429):new f(`API error ${i.status}: ${t}`,i.status,e)}if(!i.body)throw new d("No response body");yield*this.parseSSE(i.body,s)}async*parseSSE(t,e){let s=new TextDecoder,i=t.getReader(),r="",n=new Map,o={inputTokens:0,outputTokens:0},a=null;try{for(;;){if(e?.aborted)return void i.cancel();let{done:t,value:l}=await i.read();if(t)break;r+=s.decode(l,{stream:!0});let h=r.split("\n");r=h.pop()??"";for(let t of h){if(!t.startsWith("data: "))continue;let e,s=t.slice(6);if("[DONE]"===s)continue;try{e=JSON.parse(s)}catch{continue}let i=e.type;if("message_start"===i){let t=e.message?.usage;t&&(o={inputTokens:t.input_tokens??0,outputTokens:t.output_tokens??0,cacheCreationInputTokens:t.cache_creation_input_tokens,cacheReadInputTokens:t.cache_read_input_tokens});continue}if("content_block_start"===i){let t=e.index,s=e.content_block;"tool_use"===s.type?(n.set(t,{type:"tool_use",id:s.id,name:s.name,input:""}),yield{type:"tool_use_start",id:s.id,name:s.name}):"text"===s.type?n.set(t,{type:"text",text:""}):"thinking"===s.type&&n.set(t,{type:"thinking",thinking:""});continue}if("content_block_delta"===i){let t=e.index,s=n.get(t),i=e.delta;"text_delta"===i.type&&void 0!==i.text?(s&&(s.text=(s.text??"")+i.text),i.text&&(yield{type:"text_delta",text:i.text})):"thinking_delta"===i.type&&void 0!==i.thinking?(s&&(s.thinking=(s.thinking??"")+i.thinking),i.thinking&&(yield{type:"thinking_delta",text:i.thinking})):"input_json_delta"===i.type&&void 0!==i.partial_json&&(s&&(s.input=(s.input??"")+i.partial_json),i.partial_json&&(yield{type:"tool_use_delta",partialInput:i.partial_json}));continue}if("content_block_stop"===i){let t=e.index,s=n.get(t);if("tool_use"===s?.type&&s.id&&s.name){let t={};try{t=JSON.parse(s.input??"{}")}catch{}yield{type:"tool_use_end",id:s.id,name:s.name,input:t}}continue}if("message_delta"===i){let t=e.delta;t?.stop_reason&&(a=t.stop_reason);let s=e.usage;s?.output_tokens&&(o={...o,outputTokens:s.output_tokens});continue}"message_stop"===i&&(yield{type:"message_stop",usage:o,stopReason:a})}}}finally{i.releaseLock()}}buildHeaders(t){let e=this.buildBetas(t);return{"Content-Type":"application/json",Authorization:`Bearer ${this.accessToken}`,"anthropic-version":"2023-06-01","anthropic-beta":e.join(","),"x-app":"cli","User-Agent":`claude-code/${this.version} (external, sdk)`,"X-Claude-Code-Session-Id":this.sessionId}}buildRequestBody(t){let e,s=this.computeFingerprint(t.messages),i=`x-anthropic-billing-header: cc_version=${this.version}.${s}; cc_entrypoint=cli;`;e="string"==typeof t.system?i+"\n"+t.system:Array.isArray(t.system)?[{type:"text",text:i},...t.system]:i;let r={model:t.model,messages:t.messages,max_tokens:t.maxTokens??16384,stream:!0,system:e,metadata:{user_id:JSON.stringify({device_id:this.deviceId,account_uuid:this.accountUuid,session_id:this.sessionId})}};t.tools&&t.tools.length>0&&(r.tools=t.tools,t.toolChoice&&(r.tool_choice="string"==typeof t.toolChoice?{type:t.toolChoice}:t.toolChoice)),!1!==t.caching&&this.addCacheMarkers(r);let n=t.model.toLowerCase(),o=n.includes("opus-4-6")||n.includes("sonnet-4-6"),a="disabled"===t.thinking?.type;return!a&&o?r.thinking={type:"adaptive"}:"enabled"===t.thinking?.type&&(r.thinking={type:"enabled",budget_tokens:t.thinking.budgetTokens}),!(!a&&(o||"enabled"===t.thinking?.type))&&void 0!==t.temperature&&(r.temperature=t.temperature),void 0!==t.topP&&(r.top_p=t.topP),t.effort&&o&&(r.output_config={effort:t.effort}),t.stopSequences?.length&&(r.stop_sequences=t.stopSequences),t.fast&&(r.speed="fast"),r}addCacheMarkers(t){let e=t.system;if("string"==typeof e)t.system=[{type:"text",text:e,cache_control:{type:"ephemeral"}}];else if(Array.isArray(e)){let t=e;t.length>0&&(t[t.length-1]={...t[t.length-1],cache_control:{type:"ephemeral"}})}let s=t.messages;if(0===s.length)return;let i=s[s.length-1];if("string"==typeof i.content)i.content=[{type:"text",text:i.content,cache_control:{type:"ephemeral"}}];else if(Array.isArray(i.content)&&i.content.length>0){let t=i.content[i.content.length-1];i.content[i.content.length-1]={...t,cache_control:{type:"ephemeral"}}}}buildBetas(t){let e=[],s=t.model.toLowerCase().includes("haiku");return s||e.push("claude-code-20250219"),e.push("oauth-2025-04-20"),/\[1m\]/i.test(t.model)&&e.push("context-1m-2025-08-07"),!s&&"disabled"!==t.thinking?.type&&e.push("interleaved-thinking-2025-05-14"),t.effort&&e.push("effort-2025-11-24"),t.fast&&e.push("fast-mode-2026-02-01"),e.push("prompt-caching-scope-2026-01-05"),t.extraBetas&&e.push(...t.extraBetas),e}async ensureAuth(){this.accessToken&&!this.isTokenExpired()||this.credentialStore.hasChanged&&await this.credentialStore.hasChanged()&&(await this.loadFromStore(),this.accessToken&&!this.isTokenExpired())||!this.accessToken&&(await this.loadFromStore(),this.accessToken&&!this.isTokenExpired())||this.accessToken&&this.isTokenExpired()&&await this.refreshTokenWithTripleCheck()}async loadFromStore(){let t=await this.credentialStore.read();if(!t?.accessToken)throw new p('No OAuth tokens found. Run "claude login" first or provide credentials.');this.accessToken=t.accessToken,this.refreshToken=t.refreshToken,this.expiresAt=t.expiresAt}isTokenExpired(){return!!this.expiresAt&&Date.now()+y>=this.expiresAt}async refreshTokenWithTripleCheck(){let t=await this.credentialStore.read();if(t&&!(Date.now()+y>=t.expiresAt))return this.accessToken=t.accessToken,this.refreshToken=t.refreshToken,void(this.expiresAt=t.expiresAt);await this.doTokenRefresh()}async handleAuth401(){let t=this.accessToken;this.pending401&&this.lastFailedToken===t||(this.lastFailedToken=t,this.pending401=(async()=>{let e=await this.credentialStore.read();return e&&e.accessToken!==t?(this.accessToken=e.accessToken,this.refreshToken=e.refreshToken,this.expiresAt=e.expiresAt,!0):(await this.doTokenRefresh(),!0)})().finally(()=>{this.pending401=null,this.lastFailedToken=null})),await this.pending401}async doTokenRefresh(){if(!this.refreshToken)throw new p("Token expired and no refresh token available.");let t=await fetch("https://platform.claude.com/v1/oauth/token",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({grant_type:"refresh_token",refresh_token:this.refreshToken,client_id:"9d1c250a-e61b-44d9-88ed-5944d1962f5e"}),signal:AbortSignal.timeout(15e3)});if(!t.ok){let e=await this.credentialStore.read();if(e&&!(Date.now()+y>=e.expiresAt))return this.accessToken=e.accessToken,this.refreshToken=e.refreshToken,void(this.expiresAt=e.expiresAt);throw new p(`Token refresh failed: ${t.status} ${t.statusText}`)}let e=await t.json();this.accessToken=e.access_token,this.refreshToken=e.refresh_token??this.refreshToken,this.expiresAt=Date.now()+1e3*e.expires_in,await this.credentialStore.write({accessToken:this.accessToken,refreshToken:this.refreshToken,expiresAt:this.expiresAt})}computeFingerprint(t){let e=t.find(t=>"user"===t.role);if(!e)return this.hashFingerprint("000");let s="";"string"==typeof e.content?s=e.content:Array.isArray(e.content)&&(s=e.content.find(t=>"text"===t.type)?.text??"");let i=[4,7,20].map(t=>s[t]||"0").join("");return this.hashFingerprint(i)}hashFingerprint(t){let e=`59cf53e54c78${t}${this.version}`;return s("sha256").update(e).digest("hex").slice(0,3)}assembleResponse(t,e){let s=[],i=[],r=[],n={inputTokens:0,outputTokens:0},o=null,a="",l="";for(let e of t)switch(e.type){case"text_delta":a+=e.text;break;case"thinking_delta":l+=e.text;break;case"tool_use_end":r.push({type:"tool_use",id:e.id,name:e.name,input:e.input});break;case"message_stop":n=e.usage,o=e.stopReason;break;case"error":throw e.error}return a&&s.push({type:"text",text:a}),l&&i.push({type:"thinking",thinking:l}),s.push(...r),{content:s,thinking:i.length>0?i:void 0,toolCalls:r.length>0?r:void 0,usage:n,stopReason:o,rateLimitInfo:this.lastRateLimitInfo,model:e}}parseRateLimitHeaders(t){let e=t.get("retry-after"),s=t.get("anthropic-ratelimit-unified-reset"),i=s?Number(s):null;return{status:t.get("anthropic-ratelimit-unified-status"),resetAt:Number.isFinite(i)?i:null,claim:t.get("anthropic-ratelimit-unified-representative-claim"),retryAfter:e?parseFloat(e):null}}getRetryDelay(t,e){if(e){let t=parseInt(e,10);if(!isNaN(t))return 1e3*t}let s=Math.min(500*Math.pow(2,t-1),32e3);return s+.25*Math.random()*s}sleep(t,e){return new Promise((s,i)=>{if(e?.aborted)return void i(new d("Aborted"));let r=setTimeout(s,t);e?.addEventListener("abort",()=>{clearTimeout(r),i(new d("Aborted"))},{once:!0})})}readAccountUuid(){try{let t=c(u(),".claude","claude_code_config.json");return JSON.parse(n(t,"utf8")).oauthAccount?.accountUuid??""}catch{return""}}},w=class{constructor(t){this.path=t}static{e(this,"FileCredentialStore")}lastMtimeMs=0;async read(){try{let t=n(this.path,"utf8");return this.lastMtimeMs=this.getMtime(),JSON.parse(t).claudeAiOauth??null}catch{return null}}async write(t){let e={};try{e=JSON.parse(n(this.path,"utf8"))}catch{}e.claudeAiOauth=t;let s=c(this.path,"..");try{l(s,{recursive:!0})}catch{}o(this.path,JSON.stringify(e,null,2),"utf8"),a(this.path,384),this.lastMtimeMs=this.getMtime()}async hasChanged(){let t=this.getMtime();return t!==this.lastMtimeMs&&(this.lastMtimeMs=t,!0)}getMtime(){try{return h(this.path).mtimeMs}catch{return 0}}},g=class{static{e(this,"MemoryCredentialStore")}credentials;constructor(t){this.credentials={...t}}async read(){return this.credentials.accessToken?{...this.credentials}:null}async write(t){this.credentials={...t}}},k=class _Conversation{static{e(this,"Conversation")}sdk;options;t=[];i={inputTokens:0,outputTokens:0};constructor(t,e){this.sdk=t,this.options=e}get messages(){return this.t}get totalUsage(){return{...this.i}}get length(){return this.t.length}async send(t,e){this.appendUserMessage(t);let s=this.buildGenerateOptions(e),i=await this.sdk.generate(s);return this.appendAssistantFromResponse(i),this.accumulateUsage(i.usage),i}async*stream(t,e){this.appendUserMessage(t);let s=this.buildGenerateOptions(e),i=[],r=[],n=[],o={inputTokens:0,outputTokens:0};for await(let t of this.sdk.stream(s))switch(yield t,t.type){case"text_delta":i.push(t.text);break;case"thinking_delta":r.push(t.text);break;case"tool_use_end":n.push({type:"tool_use",id:t.id,name:t.name,input:t.input});break;case"message_stop":o=t.usage}let a=[];i.length>0&&a.push({type:"text",text:i.join("")});for(let t of n)a.push({type:"tool_use",id:t.id,name:t.name,input:t.input});a.length>0&&this.t.push({role:"assistant",content:a}),this.accumulateUsage(o)}addToolResult(t,e,s){let i={type:"tool_result",tool_use_id:t,content:e,...s&&{is_error:!0}};this.t.push({role:"user",content:[i]})}addToolResults(t){let e=t.map(t=>({type:"tool_result",tool_use_id:t.toolUseId,content:t.content,...t.isError&&{is_error:!0}}));this.t.push({role:"user",content:e})}async continue(t){let e=this.buildGenerateOptions(t),s=await this.sdk.generate(e);return this.appendAssistantFromResponse(s),this.accumulateUsage(s.usage),s}async*continueStream(t){let e=this.buildGenerateOptions(t),s=[],i=[],r={inputTokens:0,outputTokens:0};for await(let t of this.sdk.stream(e))switch(yield t,t.type){case"text_delta":s.push(t.text);break;case"tool_use_end":i.push({type:"tool_use",id:t.id,name:t.name,input:t.input});break;case"message_stop":r=t.usage}let n=[];s.length>0&&n.push({type:"text",text:s.join("")});for(let t of i)n.push({type:"tool_use",id:t.id,name:t.name,input:t.input});n.length>0&&this.t.push({role:"assistant",content:n}),this.accumulateUsage(r)}rewind(t){if(t<0||t>=this.t.length)throw new Error(`Invalid rewind index: ${t}`);return this.t.splice(t)}undoLastTurn(){for(let t=this.t.length-1;t>=0;t--){let e=this.t[t];if("user"===e.role){let s=e.content;if(!(Array.isArray(s)&&s.length>0&&"tool_result"===s[0].type))return this.rewind(t)}}return[]}branch(){let t=new _Conversation(this.sdk,{...this.options});return t.t=[...this.t],t.i={...this.i},t}getHistory(){return this.t.map((t,e)=>{let s="";if("string"==typeof t.content)s=t.content.slice(0,100);else if(Array.isArray(t.content)){let e=t.content[0];"text"===e?.type?s=e.text?.slice(0,100)??"":"tool_result"===e?.type?s=`[tool_result: ${e.tool_use_id}]`:"tool_use"===e?.type&&(s=`[tool_use: ${e.name}]`)}return{index:e,role:t.role,preview:s}})}appendUserMessage(t){this.t.push({role:"user",content:t})}appendAssistantFromResponse(t){let e=[];for(let s of t.content)"text"===s.type?e.push({type:"text",text:s.text}):"tool_use"===s.type&&e.push({type:"tool_use",id:s.id,name:s.name,input:s.input});e.length>0&&this.t.push({role:"assistant",content:e})}buildGenerateOptions(t){return{model:this.options.model,messages:[...this.t],system:this.options.system,tools:t?.tools??this.options.tools,toolChoice:t?.toolChoice??this.options.toolChoice,maxTokens:this.options.maxTokens,thinking:this.options.thinking,effort:this.options.effort,fast:this.options.fast,signal:t?.signal??this.options.signal,extraBetas:this.options.extraBetas,caching:this.options.caching}}accumulateUsage(t){this.i.inputTokens+=t.inputTokens,this.i.outputTokens+=t.outputTokens,this.i.cacheCreationInputTokens=(this.i.cacheCreationInputTokens??0)+(t.cacheCreationInputTokens??0),this.i.cacheReadInputTokens=(this.i.cacheReadInputTokens??0)+(t.cacheReadInputTokens??0)}};import{readFileSync as b,writeFileSync as x,mkdirSync as T}from"fs";import{dirname as v}from"path";import{randomUUID as S}from"crypto";function C(t,e){T(v(t),{recursive:!0});let s=null,i=[];for(let t of e){let e=S(),r={type:"user"===t.role?"user":"assistant",uuid:e,parentUuid:s,timestamp:Date.now(),content:t.content};i.push(JSON.stringify(r)),s=e}x(t,i.join("\n")+"\n","utf8")}function A(t){let e=b(t,"utf8"),s=[];for(let t of e.split("\n")){if(!t.trim())continue;let e;try{e=JSON.parse(t)}catch{continue}("user"===e.type||"assistant"===e.type)&&s.push({role:"user"===e.type?"user":"assistant",content:e.content})}return s}e(C,"saveSession"),e(A,"loadSession");import{createHash as $,randomBytes as R}from"crypto";import{writeFileSync as O,readFileSync as N,mkdirSync as I,chmodSync as E}from"fs";import{dirname as U,join as L}from"path";import{homedir as D}from"os";var J="9d1c250a-e61b-44d9-88ed-5944d1962f5e",F="https://platform.claude.com",M=`${F}/oauth/authorize`,P=`${F}/v1/oauth/token`,j=`${F}/oauth/code/callback`,B=["user:profile","user:inference","org:create_api_key","user:sessions:claude_code","user:mcp_servers","user:file_upload"].join(" ");function q(){return K(R(32))}function z(t){return K($("sha256").update(t).digest())}function H(){return K(R(32))}function K(t){return t.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function G(t={}){let e=t.credentialsPath??L(D(),".claude",".credentials.json"),s=q(),i=z(s),r=H(),{port:n,waitForCode:o,close:a}=await V(r,t.port),l=`http://localhost:${n}/callback`,h=!1!==t.loginWithClaudeAi?"https://claude.com/cai/oauth/authorize":M,c=new URLSearchParams({client_id:J,response_type:"code",scope:B,code_challenge:i,code_challenge_method:"S256",state:r,code:"true"});t.loginHint&&c.set("login_hint",t.loginHint),t.loginMethod&&c.set("login_method",t.loginMethod),t.orgUUID&&c.set("orgUUID",t.orgUUID);let u,d,p=`${h}?${c.toString()}&redirect_uri=${encodeURIComponent(l)}`,f=`${h}?${c.toString()}&redirect_uri=${encodeURIComponent(j)}`;t.onAuthUrl?t.onAuthUrl(p,f):(console.log("\n🔐 Login to Claude\n"),console.log("Open this URL in your browser:\n"),console.log(` ${f}\n`)),!1!==t.openBrowser&&W(p).catch(()=>{});try{u=await o,d=l}catch(t){throw a(),t}a();let m=await fetch(P,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({grant_type:"authorization_code",code:u,redirect_uri:d,client_id:J,code_verifier:s,state:r})});if(!m.ok){let t=await m.text();throw new Error(`Token exchange failed (${m.status}): ${t}`)}let y=await m.json(),_=Date.now()+1e3*y.expires_in,w={accessToken:y.access_token,refreshToken:y.refresh_token,expiresAt:_,scopes:y.scope?.split(" ")??[]},g={};try{g=JSON.parse(N(e,"utf8"))}catch{}g.claudeAiOauth=w;let k=U(e);try{I(k,{recursive:!0})}catch{}return O(e,JSON.stringify(g,null,2),"utf8"),E(e,384),console.log(`\n✅ Login successful! Credentials saved to ${e}\n`),{accessToken:w.accessToken,refreshToken:w.refreshToken,expiresAt:w.expiresAt,credentialsPath:e}}async function V(t,s){let i,r,n=new Promise((t,e)=>{i=t,r=e}),o=Bun.serve({port:s??0,async fetch(e){let s=new URL(e.url);if("/callback"!==s.pathname)return new Response("Not found",{status:404});let n=s.searchParams.get("code"),o=s.searchParams.get("state"),a=s.searchParams.get("error");return a?(r(new Error(`OAuth error: ${a} — ${s.searchParams.get("error_description")??""}`)),new Response("<html><body><h1>Login failed</h1><p>You can close this tab.</p></body></html>",{status:400,headers:{"Content-Type":"text/html"}})):n&&o===t?(i(n),new Response(null,{status:302,headers:{Location:`${F}/oauth/code/success?app=claude-code`}})):(r(new Error("Invalid callback: missing code or state mismatch")),new Response("Invalid request",{status:400}))}}),a=setTimeout(()=>{r(new Error("Login timed out (5 minutes). Try again.")),o.stop()},3e5);return{port:o.port,waitForCode:n.finally(()=>clearTimeout(a)),close:e(()=>{clearTimeout(a),o.stop()},"close")}}async function W(t){let e=(()=>{switch(process.platform){case"darwin":return[["open",t]];case"win32":return[["cmd","/c","start",t]];default:return[["xdg-open",t],["wslview",t],["sensible-browser",t]]}})();for(let t of e)try{let e=Bun.spawn({cmd:t,stdout:"ignore",stderr:"ignore"});if(await e.exited,0===e.exitCode)return}catch{}}e(q,"generateCodeVerifier"),e(z,"generateCodeChallenge"),e(H,"generateState"),e(K,"base64url"),e(G,"oauthLogin"),e(V,"startCallbackServer"),e(W,"tryOpenBrowser");export{f as APIError,p as AuthError,_ as ClaudeCodeSDK,d as ClaudeCodeSDKError,k as Conversation,w as FileCredentialStore,g as MemoryCredentialStore,m as RateLimitError,A as loadSession,G as oauthLogin,C as saveSession};
|
package/package.json
CHANGED