@life-ai-tools/claude-code-sdk 0.1.0
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/LICENSE +21 -0
- package/README.md +252 -0
- package/dist/conversation.d.ts +81 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/sdk.d.ts +93 -0
- package/dist/session.d.ts +13 -0
- package/dist/types.d.ts +244 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 LifeAITools
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# opencode-claude-toolkit
|
|
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
|
|
8
|
+
|
|
9
|
+
> **Why this exists?** Read our [Open Letter to Anthropic](OPEN-LETTER.md) about token efficiency, developer freedom, and collaboration.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
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
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Quick Start — One Liner
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Start proxy and launch opencode in one go
|
|
52
|
+
bunx @lifeaitools/opencode-proxy &
|
|
53
|
+
LOCAL_ENDPOINT=http://localhost:4040/v1 opencode
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or step by step:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Terminal 1: Start the proxy
|
|
60
|
+
bunx @lifeaitools/opencode-proxy --port 4040
|
|
61
|
+
|
|
62
|
+
# Terminal 2: Launch opencode pointing to the proxy
|
|
63
|
+
LOCAL_ENDPOINT=http://localhost:4040/v1 opencode
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
That's it. opencode will now use your Claude Max subscription for all requests.
|
|
67
|
+
|
|
68
|
+
### Supported Models
|
|
69
|
+
|
|
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 |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## opencode Configuration
|
|
79
|
+
|
|
80
|
+
To make opencode always use the proxy, add to your `.opencode.json`:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"provider": {
|
|
85
|
+
"id": "openai",
|
|
86
|
+
"api_key": "not-needed",
|
|
87
|
+
"model": {
|
|
88
|
+
"id": "claude-v4.6-sonnet",
|
|
89
|
+
"name": "Claude Sonnet 4.6",
|
|
90
|
+
"api_model": "claude-v4.6-sonnet",
|
|
91
|
+
"can_reason": true
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Set the environment variable permanently:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Add to your .bashrc / .zshrc
|
|
101
|
+
export LOCAL_ENDPOINT=http://localhost:4040/v1
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## SDK Usage
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { ClaudeCodeSDK } from '@lifeaitools/claude-code-sdk'
|
|
110
|
+
|
|
111
|
+
const sdk = new ClaudeCodeSDK()
|
|
112
|
+
|
|
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
|
+
// Streaming
|
|
122
|
+
for await (const event of sdk.stream({
|
|
123
|
+
model: 'claude-sonnet-4-6-20250415',
|
|
124
|
+
messages: [{ role: 'user', content: 'Write a haiku about coding' }],
|
|
125
|
+
maxTokens: 1024,
|
|
126
|
+
})) {
|
|
127
|
+
if (event.type === 'text_delta') {
|
|
128
|
+
process.stdout.write(event.text)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Multi-turn conversation
|
|
133
|
+
import { Conversation } from '@lifeaitools/claude-code-sdk'
|
|
134
|
+
|
|
135
|
+
const conv = new Conversation(sdk, { model: 'claude-sonnet-4-6-20250415' })
|
|
136
|
+
const reply1 = await conv.send('What is TypeScript?')
|
|
137
|
+
const reply2 = await conv.send('How does it compare to JavaScript?')
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
See [`examples/`](examples/) for more usage patterns.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Features
|
|
145
|
+
|
|
146
|
+
- **Zero API key** — uses your existing Claude Max/Pro OAuth credentials from `~/.claude`
|
|
147
|
+
- **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
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Troubleshooting
|
|
160
|
+
|
|
161
|
+
### "No credentials found"
|
|
162
|
+
```
|
|
163
|
+
Error: No credentials found. Run `claude` first or provide credentials.
|
|
164
|
+
```
|
|
165
|
+
**Fix:** Run `claude` in your terminal, complete the OAuth login, then try again. The file `~/.claude/.credentials.json` must exist.
|
|
166
|
+
|
|
167
|
+
### "Rate limited" / 429 errors
|
|
168
|
+
```
|
|
169
|
+
[proxy] error: Rate limited: ...
|
|
170
|
+
```
|
|
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
|
+
|
|
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`
|
|
178
|
+
|
|
179
|
+
### Proxy won't start
|
|
180
|
+
```bash
|
|
181
|
+
# Check if port 4040 is already in use
|
|
182
|
+
lsof -i :4040
|
|
183
|
+
|
|
184
|
+
# Use a different port
|
|
185
|
+
bunx @lifeaitools/opencode-proxy --port 4041
|
|
186
|
+
```
|
|
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
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## How It Works
|
|
199
|
+
|
|
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
|
|
211
|
+
```
|
|
212
|
+
|
|
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.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Project Structure
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
opencode-claude-toolkit/
|
|
221
|
+
├── packages/
|
|
222
|
+
│ └── opencode-proxy/ # OpenAI-compatible proxy (open source)
|
|
223
|
+
│ ├── server.ts # HTTP server (Bun.serve)
|
|
224
|
+
│ ├── translate.ts # OpenAI ↔ Claude format translation
|
|
225
|
+
│ └── launch.ts # Auto-launcher
|
|
226
|
+
├── dist/ # Compiled SDK (published to npm)
|
|
227
|
+
├── examples/ # Usage examples
|
|
228
|
+
│ ├── basic-chat.ts
|
|
229
|
+
│ └── conversation.ts
|
|
230
|
+
├── OPEN-LETTER.md # Our message to Anthropic
|
|
231
|
+
├── REQUEST-SOURCE.md # How to request SDK source access
|
|
232
|
+
├── LICENSE # MIT
|
|
233
|
+
└── README.md
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
MIT — see [LICENSE](LICENSE)
|
|
241
|
+
|
|
242
|
+
## Source Access
|
|
243
|
+
|
|
244
|
+
The SDK is distributed as a compiled bundle. If you need source access for auditing, contributions, or enterprise use, see [REQUEST-SOURCE.md](REQUEST-SOURCE.md).
|
|
245
|
+
|
|
246
|
+
## Open Letter
|
|
247
|
+
|
|
248
|
+
We built this with respect and appreciation for Anthropic's work. Read our [Open Letter to Anthropic](OPEN-LETTER.md) about why this project exists and our invitation to collaborate.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
Built by [LifeAITools](https://lifeaitools.com) | [GitHub](https://github.com/LifeAITools)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { ClaudeCodeSDK } from './sdk.js';
|
|
2
|
+
import type { MessageParam, ContentBlockParam, ConversationOptions, TurnOptions, GenerateResponse, StreamEvent, TokenUsage } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Multi-turn conversation wrapper around ClaudeCodeSDK.
|
|
5
|
+
*
|
|
6
|
+
* Accumulates messages across turns, handles prompt caching,
|
|
7
|
+
* supports tool execution loop, rewind, and branching.
|
|
8
|
+
*
|
|
9
|
+
* Pattern mirrors CLI's query loop (query.ts:204-321):
|
|
10
|
+
* messages accumulate in mutable state across iterations.
|
|
11
|
+
*/
|
|
12
|
+
export declare class Conversation {
|
|
13
|
+
private sdk;
|
|
14
|
+
private options;
|
|
15
|
+
private _messages;
|
|
16
|
+
private _totalUsage;
|
|
17
|
+
constructor(sdk: ClaudeCodeSDK, options: ConversationOptions);
|
|
18
|
+
/** Read-only access to conversation history */
|
|
19
|
+
get messages(): readonly MessageParam[];
|
|
20
|
+
/** Cumulative token usage across all turns */
|
|
21
|
+
get totalUsage(): TokenUsage;
|
|
22
|
+
/** Number of messages in conversation */
|
|
23
|
+
get length(): number;
|
|
24
|
+
/** Send a user message and get complete response */
|
|
25
|
+
send(content: string | ContentBlockParam[], turnOptions?: TurnOptions): Promise<GenerateResponse>;
|
|
26
|
+
/** Send a user message and stream the response */
|
|
27
|
+
stream(content: string | ContentBlockParam[], turnOptions?: TurnOptions): AsyncGenerator<StreamEvent>;
|
|
28
|
+
/**
|
|
29
|
+
* Add a tool result to the conversation.
|
|
30
|
+
* Call this after executing a tool_use returned by the model.
|
|
31
|
+
* Then call send() or stream() with the next user message (or empty)
|
|
32
|
+
* to continue the conversation.
|
|
33
|
+
*/
|
|
34
|
+
addToolResult(toolUseId: string, content: string | ContentBlockParam[], isError?: boolean): void;
|
|
35
|
+
/**
|
|
36
|
+
* Add multiple tool results at once (for parallel tool execution).
|
|
37
|
+
*/
|
|
38
|
+
addToolResults(results: Array<{
|
|
39
|
+
toolUseId: string;
|
|
40
|
+
content: string | ContentBlockParam[];
|
|
41
|
+
isError?: boolean;
|
|
42
|
+
}>): void;
|
|
43
|
+
/**
|
|
44
|
+
* Continue conversation after adding tool results.
|
|
45
|
+
* Sends accumulated tool results to the model.
|
|
46
|
+
*/
|
|
47
|
+
continue(turnOptions?: TurnOptions): Promise<GenerateResponse>;
|
|
48
|
+
/**
|
|
49
|
+
* Continue conversation with streaming after adding tool results.
|
|
50
|
+
*/
|
|
51
|
+
continueStream(turnOptions?: TurnOptions): AsyncGenerator<StreamEvent>;
|
|
52
|
+
/**
|
|
53
|
+
* Rewind conversation to a specific message index.
|
|
54
|
+
* Removes all messages at and after the index.
|
|
55
|
+
* Returns removed messages.
|
|
56
|
+
*/
|
|
57
|
+
rewind(toIndex: number): MessageParam[];
|
|
58
|
+
/**
|
|
59
|
+
* Rewind to the last user message (undo last turn).
|
|
60
|
+
* Removes the last assistant + any tool results after it.
|
|
61
|
+
*/
|
|
62
|
+
undoLastTurn(): MessageParam[];
|
|
63
|
+
/**
|
|
64
|
+
* Branch conversation — create new Conversation with messages up to current point.
|
|
65
|
+
* Like CLI's /branch command.
|
|
66
|
+
*/
|
|
67
|
+
branch(): Conversation;
|
|
68
|
+
/**
|
|
69
|
+
* Get message history with indices for rewind UI.
|
|
70
|
+
*/
|
|
71
|
+
getHistory(): Array<{
|
|
72
|
+
index: number;
|
|
73
|
+
role: string;
|
|
74
|
+
preview: string;
|
|
75
|
+
}>;
|
|
76
|
+
private appendUserMessage;
|
|
77
|
+
private appendAssistantFromResponse;
|
|
78
|
+
private buildGenerateOptions;
|
|
79
|
+
private accumulateUsage;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=conversation.d.ts.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { ClaudeCodeSDK, FileCredentialStore, MemoryCredentialStore } from './sdk.js';
|
|
2
|
+
export { Conversation } from './conversation.js';
|
|
3
|
+
export { saveSession, loadSession } from './session.js';
|
|
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
|
+
export { ClaudeCodeSDKError, AuthError, APIError, RateLimitError, } from './types.js';
|
|
6
|
+
export type { SessionEntry } from './session.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claude-code-sdk — TypeScript SDK for Claude Code API
|
|
3
|
+
* (c) 2026 Kiberos. Compiled distribution.
|
|
4
|
+
* Source access: see REQUEST-SOURCE.md in the GitHub repo.
|
|
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};
|
package/dist/sdk.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { ClaudeCodeSDKOptions, CredentialStore, StoredCredentials, GenerateOptions, GenerateResponse, StreamEvent, RateLimitInfo } from './types.js';
|
|
2
|
+
export declare class ClaudeCodeSDK {
|
|
3
|
+
private accessToken;
|
|
4
|
+
private refreshToken;
|
|
5
|
+
private expiresAt;
|
|
6
|
+
private credentialStore;
|
|
7
|
+
private sessionId;
|
|
8
|
+
private deviceId;
|
|
9
|
+
private accountUuid;
|
|
10
|
+
private version;
|
|
11
|
+
private timeout;
|
|
12
|
+
private maxRetries;
|
|
13
|
+
private lastRateLimitInfo;
|
|
14
|
+
private pending401;
|
|
15
|
+
private lastFailedToken;
|
|
16
|
+
constructor(options?: ClaudeCodeSDKOptions);
|
|
17
|
+
/** Non-streaming: send messages, get full response */
|
|
18
|
+
generate(options: GenerateOptions): Promise<GenerateResponse>;
|
|
19
|
+
/** Streaming: yields events as they arrive from SSE */
|
|
20
|
+
stream(options: GenerateOptions): AsyncGenerator<StreamEvent>;
|
|
21
|
+
getRateLimitInfo(): RateLimitInfo;
|
|
22
|
+
private doStreamRequest;
|
|
23
|
+
private parseSSE;
|
|
24
|
+
/** HTTP headers — mimics getAnthropicClient() + getAuthHeaders() */
|
|
25
|
+
private buildHeaders;
|
|
26
|
+
/** Request body — mirrors paramsFromContext() in claude.ts:1699 */
|
|
27
|
+
private buildRequestBody;
|
|
28
|
+
/** Add cache_control markers to system + last message — mirrors addCacheBreakpoints() */
|
|
29
|
+
private addCacheMarkers;
|
|
30
|
+
/** Beta headers — mirrors getAllModelBetas() in betas.ts:234 */
|
|
31
|
+
private buildBetas;
|
|
32
|
+
/**
|
|
33
|
+
* Ensure valid auth token before API call.
|
|
34
|
+
* Mirrors checkAndRefreshOAuthTokenIfNeeded() from auth.ts:1427.
|
|
35
|
+
*
|
|
36
|
+
* Triple-check pattern:
|
|
37
|
+
* 1. Check cached token in memory
|
|
38
|
+
* 2. If expired, check store (another process may have refreshed)
|
|
39
|
+
* 3. If still expired, do the refresh
|
|
40
|
+
*/
|
|
41
|
+
private ensureAuth;
|
|
42
|
+
/** Load credentials from the credential store */
|
|
43
|
+
private loadFromStore;
|
|
44
|
+
/** 5-minute buffer before actual expiry — from oauth/client.ts:344-353 */
|
|
45
|
+
private isTokenExpired;
|
|
46
|
+
/**
|
|
47
|
+
* Triple-check refresh — mirrors auth.ts:1472-1556.
|
|
48
|
+
* Check store again (race), then refresh, then check store on error.
|
|
49
|
+
*/
|
|
50
|
+
private refreshTokenWithTripleCheck;
|
|
51
|
+
/**
|
|
52
|
+
* Handle 401 error — mirrors handleOAuth401Error() from auth.ts:1338-1392.
|
|
53
|
+
* Deduplicates concurrent 401 handlers for the same failed token.
|
|
54
|
+
*/
|
|
55
|
+
handleAuth401(): Promise<void>;
|
|
56
|
+
/** POST to platform.claude.com/v1/oauth/token — from oauth/client.ts:146 */
|
|
57
|
+
private doTokenRefresh;
|
|
58
|
+
computeFingerprint(messages: {
|
|
59
|
+
role: string;
|
|
60
|
+
content: string | unknown[];
|
|
61
|
+
}[]): string;
|
|
62
|
+
private hashFingerprint;
|
|
63
|
+
private assembleResponse;
|
|
64
|
+
private parseRateLimitHeaders;
|
|
65
|
+
private getRetryDelay;
|
|
66
|
+
private sleep;
|
|
67
|
+
private readAccountUuid;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* File-based credential store with mtime detection.
|
|
71
|
+
* Mirrors CLI's plainTextStorage.ts + invalidateOAuthCacheIfDiskChanged().
|
|
72
|
+
*/
|
|
73
|
+
export declare class FileCredentialStore implements CredentialStore {
|
|
74
|
+
private path;
|
|
75
|
+
private lastMtimeMs;
|
|
76
|
+
constructor(path: string);
|
|
77
|
+
read(): Promise<StoredCredentials | null>;
|
|
78
|
+
write(credentials: StoredCredentials): Promise<void>;
|
|
79
|
+
/** Detect cross-process changes via mtime — from auth.ts:1313-1336 */
|
|
80
|
+
hasChanged(): Promise<boolean>;
|
|
81
|
+
private getMtime;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* In-memory credential store for direct token injection.
|
|
85
|
+
* No persistence — tokens live only in SDK instance.
|
|
86
|
+
*/
|
|
87
|
+
export declare class MemoryCredentialStore implements CredentialStore {
|
|
88
|
+
private credentials;
|
|
89
|
+
constructor(initial: StoredCredentials);
|
|
90
|
+
read(): Promise<StoredCredentials | null>;
|
|
91
|
+
write(credentials: StoredCredentials): Promise<void>;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=sdk.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MessageParam } from './types.js';
|
|
2
|
+
export interface SessionEntry {
|
|
3
|
+
type: 'user' | 'assistant';
|
|
4
|
+
uuid: string;
|
|
5
|
+
parentUuid: string | null;
|
|
6
|
+
timestamp: number;
|
|
7
|
+
content: MessageParam['content'];
|
|
8
|
+
}
|
|
9
|
+
/** Save conversation messages to JSONL file (CLI-compatible format) */
|
|
10
|
+
export declare function saveSession(path: string, messages: readonly MessageParam[]): void;
|
|
11
|
+
/** Load conversation messages from JSONL file */
|
|
12
|
+
export declare function loadSession(path: string): MessageParam[];
|
|
13
|
+
//# sourceMappingURL=session.d.ts.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable credential storage interface.
|
|
3
|
+
* Implement this to store tokens in a database, Redis, KV store, etc.
|
|
4
|
+
* Mirrors CLI's SecureStorage pattern (plainTextStorage.ts).
|
|
5
|
+
*/
|
|
6
|
+
export interface CredentialStore {
|
|
7
|
+
/** Read current credentials. Return null if not found. */
|
|
8
|
+
read(): Promise<StoredCredentials | null>;
|
|
9
|
+
/** Write updated credentials after refresh. */
|
|
10
|
+
write(credentials: StoredCredentials): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Check if credentials changed externally (another process refreshed).
|
|
13
|
+
* Return true if stale → SDK will re-read.
|
|
14
|
+
* Optional: if not implemented, SDK always re-reads before refresh.
|
|
15
|
+
*/
|
|
16
|
+
hasChanged?(): Promise<boolean>;
|
|
17
|
+
}
|
|
18
|
+
/** Credentials as stored/retrieved by CredentialStore */
|
|
19
|
+
export interface StoredCredentials {
|
|
20
|
+
accessToken: string;
|
|
21
|
+
refreshToken: string;
|
|
22
|
+
expiresAt: number;
|
|
23
|
+
scopes?: string[];
|
|
24
|
+
subscriptionType?: string | null;
|
|
25
|
+
rateLimitTier?: string | null;
|
|
26
|
+
}
|
|
27
|
+
/** SDK init options. Tokens can come from file OR be passed directly. */
|
|
28
|
+
export interface ClaudeCodeSDKOptions {
|
|
29
|
+
/** Direct access token. If provided, skips file reading. */
|
|
30
|
+
accessToken?: string;
|
|
31
|
+
/** Direct refresh token for auto-refresh. */
|
|
32
|
+
refreshToken?: string;
|
|
33
|
+
/** Expiry timestamp (ms). Required with accessToken if you want auto-refresh. */
|
|
34
|
+
expiresAt?: number;
|
|
35
|
+
/** Path to .credentials.json. Defaults to ~/.claude/.credentials.json */
|
|
36
|
+
credentialsPath?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Custom credential store (DB, Redis, etc.).
|
|
39
|
+
* If provided, overrides credentialsPath and direct token options.
|
|
40
|
+
*/
|
|
41
|
+
credentialStore?: CredentialStore;
|
|
42
|
+
/** device_id for metadata. If omitted, generates random 64-byte hex. */
|
|
43
|
+
deviceId?: string;
|
|
44
|
+
/** account_uuid for metadata. If omitted, reads from ~/.claude/config.json. */
|
|
45
|
+
accountUuid?: string;
|
|
46
|
+
/** SDK version string used in fingerprint + User-Agent. */
|
|
47
|
+
version?: string;
|
|
48
|
+
/** Request timeout in ms. Default: 600_000 */
|
|
49
|
+
timeout?: number;
|
|
50
|
+
/** Max retries for 5xx/529. Default: 3. Note: 429 is NEVER retried for subscribers. */
|
|
51
|
+
maxRetries?: number;
|
|
52
|
+
}
|
|
53
|
+
/** What we read from ~/.claude/.credentials.json */
|
|
54
|
+
export interface CredentialsFile {
|
|
55
|
+
claudeAiOauth?: {
|
|
56
|
+
accessToken: string;
|
|
57
|
+
refreshToken: string;
|
|
58
|
+
expiresAt: number;
|
|
59
|
+
scopes?: string[];
|
|
60
|
+
subscriptionType?: string | null;
|
|
61
|
+
rateLimitTier?: string | null;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Message param — mirrors Anthropic API */
|
|
65
|
+
export interface MessageParam {
|
|
66
|
+
role: 'user' | 'assistant';
|
|
67
|
+
content: string | ContentBlockParam[];
|
|
68
|
+
}
|
|
69
|
+
export type ContentBlockParam = TextBlockParam | {
|
|
70
|
+
type: 'image';
|
|
71
|
+
source: {
|
|
72
|
+
type: 'base64';
|
|
73
|
+
media_type: string;
|
|
74
|
+
data: string;
|
|
75
|
+
};
|
|
76
|
+
} | {
|
|
77
|
+
type: 'tool_use';
|
|
78
|
+
id: string;
|
|
79
|
+
name: string;
|
|
80
|
+
input: unknown;
|
|
81
|
+
} | {
|
|
82
|
+
type: 'tool_result';
|
|
83
|
+
tool_use_id: string;
|
|
84
|
+
content: string | ContentBlockParam[];
|
|
85
|
+
is_error?: boolean;
|
|
86
|
+
};
|
|
87
|
+
export interface TextBlockParam {
|
|
88
|
+
type: 'text';
|
|
89
|
+
text: string;
|
|
90
|
+
cache_control?: {
|
|
91
|
+
type: 'ephemeral';
|
|
92
|
+
ttl?: '1h';
|
|
93
|
+
scope?: 'global';
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** Tool definition */
|
|
97
|
+
export interface ToolDef {
|
|
98
|
+
name: string;
|
|
99
|
+
description: string;
|
|
100
|
+
input_schema: Record<string, unknown>;
|
|
101
|
+
}
|
|
102
|
+
/** System prompt blocks */
|
|
103
|
+
export type SystemParam = string | {
|
|
104
|
+
type: 'text';
|
|
105
|
+
text: string;
|
|
106
|
+
}[];
|
|
107
|
+
/** Tool choice — how the model selects tools */
|
|
108
|
+
export type ToolChoice = 'auto' | 'any' | {
|
|
109
|
+
type: 'tool';
|
|
110
|
+
name: string;
|
|
111
|
+
};
|
|
112
|
+
/** Options for a single generate/stream call */
|
|
113
|
+
export interface GenerateOptions {
|
|
114
|
+
model: string;
|
|
115
|
+
messages: MessageParam[];
|
|
116
|
+
system?: SystemParam;
|
|
117
|
+
maxTokens?: number;
|
|
118
|
+
thinking?: {
|
|
119
|
+
type: 'enabled';
|
|
120
|
+
budgetTokens: number;
|
|
121
|
+
} | {
|
|
122
|
+
type: 'disabled';
|
|
123
|
+
};
|
|
124
|
+
tools?: ToolDef[];
|
|
125
|
+
toolChoice?: ToolChoice;
|
|
126
|
+
temperature?: number;
|
|
127
|
+
topP?: number;
|
|
128
|
+
effort?: 'low' | 'medium' | 'high';
|
|
129
|
+
signal?: AbortSignal;
|
|
130
|
+
stopSequences?: string[];
|
|
131
|
+
extraBetas?: string[];
|
|
132
|
+
fast?: boolean;
|
|
133
|
+
/** Enable prompt caching. Default: true */
|
|
134
|
+
caching?: boolean;
|
|
135
|
+
}
|
|
136
|
+
/** Options for Conversation class */
|
|
137
|
+
export interface ConversationOptions {
|
|
138
|
+
model: string;
|
|
139
|
+
system?: SystemParam;
|
|
140
|
+
tools?: ToolDef[];
|
|
141
|
+
toolChoice?: ToolChoice;
|
|
142
|
+
maxTokens?: number;
|
|
143
|
+
thinking?: {
|
|
144
|
+
type: 'enabled';
|
|
145
|
+
budgetTokens: number;
|
|
146
|
+
} | {
|
|
147
|
+
type: 'disabled';
|
|
148
|
+
};
|
|
149
|
+
effort?: 'low' | 'medium' | 'high';
|
|
150
|
+
fast?: boolean;
|
|
151
|
+
signal?: AbortSignal;
|
|
152
|
+
extraBetas?: string[];
|
|
153
|
+
/** Enable prompt caching. Default: true */
|
|
154
|
+
caching?: boolean;
|
|
155
|
+
}
|
|
156
|
+
/** Turn options — per-send overrides */
|
|
157
|
+
export interface TurnOptions {
|
|
158
|
+
signal?: AbortSignal;
|
|
159
|
+
/** Override tools for this turn */
|
|
160
|
+
tools?: ToolDef[];
|
|
161
|
+
toolChoice?: ToolChoice;
|
|
162
|
+
}
|
|
163
|
+
/** Normalized stream events */
|
|
164
|
+
export type StreamEvent = {
|
|
165
|
+
type: 'text_delta';
|
|
166
|
+
text: string;
|
|
167
|
+
} | {
|
|
168
|
+
type: 'thinking_delta';
|
|
169
|
+
text: string;
|
|
170
|
+
} | {
|
|
171
|
+
type: 'tool_use_start';
|
|
172
|
+
id: string;
|
|
173
|
+
name: string;
|
|
174
|
+
} | {
|
|
175
|
+
type: 'tool_use_delta';
|
|
176
|
+
partialInput: string;
|
|
177
|
+
} | {
|
|
178
|
+
type: 'tool_use_end';
|
|
179
|
+
id: string;
|
|
180
|
+
name: string;
|
|
181
|
+
input: unknown;
|
|
182
|
+
} | {
|
|
183
|
+
type: 'message_stop';
|
|
184
|
+
usage: TokenUsage;
|
|
185
|
+
stopReason: string | null;
|
|
186
|
+
} | {
|
|
187
|
+
type: 'error';
|
|
188
|
+
error: Error;
|
|
189
|
+
};
|
|
190
|
+
export interface TokenUsage {
|
|
191
|
+
inputTokens: number;
|
|
192
|
+
outputTokens: number;
|
|
193
|
+
cacheCreationInputTokens?: number;
|
|
194
|
+
cacheReadInputTokens?: number;
|
|
195
|
+
}
|
|
196
|
+
export interface RateLimitInfo {
|
|
197
|
+
status: string | null;
|
|
198
|
+
resetAt: number | null;
|
|
199
|
+
claim: string | null;
|
|
200
|
+
retryAfter: number | null;
|
|
201
|
+
}
|
|
202
|
+
export interface GenerateResponse {
|
|
203
|
+
content: ContentBlock[];
|
|
204
|
+
thinking?: ThinkingBlock[];
|
|
205
|
+
toolCalls?: ToolUseBlock[];
|
|
206
|
+
usage: TokenUsage;
|
|
207
|
+
stopReason: string | null;
|
|
208
|
+
rateLimitInfo: RateLimitInfo;
|
|
209
|
+
model: string;
|
|
210
|
+
}
|
|
211
|
+
/** Content blocks from API response */
|
|
212
|
+
export type ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock;
|
|
213
|
+
export interface TextBlock {
|
|
214
|
+
type: 'text';
|
|
215
|
+
text: string;
|
|
216
|
+
}
|
|
217
|
+
export interface ThinkingBlock {
|
|
218
|
+
type: 'thinking';
|
|
219
|
+
thinking: string;
|
|
220
|
+
}
|
|
221
|
+
export interface ToolUseBlock {
|
|
222
|
+
type: 'tool_use';
|
|
223
|
+
id: string;
|
|
224
|
+
name: string;
|
|
225
|
+
input: unknown;
|
|
226
|
+
}
|
|
227
|
+
export declare class ClaudeCodeSDKError extends Error {
|
|
228
|
+
readonly cause?: unknown | undefined;
|
|
229
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
230
|
+
}
|
|
231
|
+
export declare class AuthError extends ClaudeCodeSDKError {
|
|
232
|
+
constructor(message: string, cause?: unknown);
|
|
233
|
+
}
|
|
234
|
+
export declare class APIError extends ClaudeCodeSDKError {
|
|
235
|
+
readonly status: number;
|
|
236
|
+
readonly requestId: string | null;
|
|
237
|
+
constructor(message: string, status: number, requestId: string | null, cause?: unknown);
|
|
238
|
+
}
|
|
239
|
+
export declare class RateLimitError extends ClaudeCodeSDKError {
|
|
240
|
+
readonly rateLimitInfo: RateLimitInfo;
|
|
241
|
+
readonly status: number;
|
|
242
|
+
constructor(message: string, rateLimitInfo: RateLimitInfo, status?: number, cause?: unknown);
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=types.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@life-ai-tools/claude-code-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript SDK for Claude Code API — use your Claude Max/Pro subscription programmatically",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "bun run scripts/build-sdk.ts",
|
|
16
|
+
"build:dev": "tsc",
|
|
17
|
+
"test": "bun test",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"prepublishOnly": "echo 'dist/ already built'"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/bun": "latest",
|
|
24
|
+
"esbuild": "^0.27.4",
|
|
25
|
+
"terser": "^5.46.1",
|
|
26
|
+
"typescript": "^5.8.0"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20.0.0",
|
|
30
|
+
"bun": ">=1.0.0"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"claude",
|
|
34
|
+
"anthropic",
|
|
35
|
+
"claude-code",
|
|
36
|
+
"claude-max",
|
|
37
|
+
"sdk",
|
|
38
|
+
"openai-compatible",
|
|
39
|
+
"streaming"
|
|
40
|
+
],
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/LifeAITools/opencode-claude-toolkit"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/LifeAITools/opencode-claude-toolkit",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"files": [
|
|
48
|
+
"dist/index.js",
|
|
49
|
+
"dist/*.d.ts",
|
|
50
|
+
"README.md",
|
|
51
|
+
"LICENSE"
|
|
52
|
+
]
|
|
53
|
+
}
|