@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 CHANGED
@@ -1,81 +1,144 @@
1
1
  # opencode-claude-toolkit
2
2
 
3
- Use your **Claude Max/Pro subscription** programmatically — no API key needed.
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
- ## Prerequisites
14
-
15
- Before you start, make sure you have:
16
-
17
- 1. **Bun** (runtime for the proxy server):
18
- ```bash
19
- curl -fsSL https://bun.sh/install | bash
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
- ## Quick Start — One Liner
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
- # Start proxy and launch opencode in one go
52
- bunx @lifeaitools/opencode-proxy &
53
- LOCAL_ENDPOINT=http://localhost:4040/v1 opencode
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
- Or step by step:
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
- ```bash
59
- # Terminal 1: Start the proxy
60
- bunx @lifeaitools/opencode-proxy --port 4040
38
+ ### Per-Project Credentials
61
39
 
62
- # Terminal 2: Launch opencode pointing to the proxy
63
- LOCAL_ENDPOINT=http://localhost:4040/v1 opencode
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
- That's it. opencode will now use your Claude Max subscription for all requests.
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 ID | Maps to | Best for |
71
- |----------|---------|----------|
72
- | `claude-v4.6-sonnet` | Claude Sonnet 4.6 | Fast coding, daily driver |
73
- | `claude-v4.6-opus` | Claude Opus 4.6 | Complex reasoning, architecture |
74
- | `claude-v4.5-haiku` | Claude Haiku 4.5 | Quick tasks, low latency |
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
- ## opencode Configuration
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
- Set the environment variable permanently:
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 Usage
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 '@lifeaitools/claude-code-sdk'
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 '@lifeaitools/claude-code-sdk'
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 your existing Claude Max/Pro OAuth credentials from `~/.claude`
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 are refreshed automatically when expired
149
- - **Retry logic** — exponential backoff for 5xx errors, proper 429 handling
150
- - **Tool use** — full support for function calling
151
- - **Thinking** — extended thinking / chain-of-thought support
152
- - **Caching** — prompt caching support for cost efficiency
153
- - **Conversation** — stateful multi-turn conversation management
154
- - **OpenAI-compatible** — proxy translates OpenAI format to/from Claude
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
- ## Troubleshooting
224
+ ## How It Works
160
225
 
161
- ### "No credentials found"
226
+ ### Plugin (direct)
162
227
  ```
163
- Error: No credentials found. Run `claude` first or provide credentials.
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
- ### "Rate limited" / 429 errors
235
+ ### Proxy (OpenAI-compatible)
168
236
  ```
169
- [proxy] error: Rate limited: ...
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
- ### "Stream idle timeout"
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
- ### Proxy won't start
180
- ```bash
181
- # Check if port 4040 is already in use
182
- lsof -i :4040
246
+ ## Troubleshooting
183
247
 
184
- # Use a different port
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
- ## How It Works
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
- ┌─────────────┐ OpenAI format ┌──────────────────┐ Claude API ┌──────────────┐
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
- The proxy translates between OpenAI's chat completion format and Claude's native API format. Streaming is end-to-end — chunks flow from Anthropic through the proxy to your client with minimal buffering.
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
- └── opencode-proxy/ # OpenAI-compatible proxy (open source)
223
- ├── server.ts # HTTP server (Bun.serve)
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 (published to npm)
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 # How to request SDK source access
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@life-ai-tools/claude-code-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "TypeScript SDK for Claude Code API — use your Claude Max/Pro subscription programmatically",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",