@swttch/extend-kit 0.5.0 → 0.6.1
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.ko.md +29 -1
- package/README.md +37 -1
- package/dist/battery/api/client.d.ts +10 -1
- package/dist/battery/api/client.js +71 -7
- package/dist/battery/api/client.js.map +1 -1
- package/dist/battery/api/http.d.ts +38 -0
- package/dist/battery/api/http.js +178 -0
- package/dist/battery/api/http.js.map +1 -0
- package/dist/battery/api/index.d.ts +2 -1
- package/dist/battery/api/index.js.map +1 -1
- package/dist/battery/api/types.d.ts +44 -0
- package/dist/battery/errors.d.ts +24 -1
- package/dist/battery/errors.js +4 -1
- package/dist/battery/errors.js.map +1 -1
- package/dist/battery/index.d.ts +2 -1
- package/dist/battery/index.js.map +1 -1
- package/dist/proxy.d.ts +61 -0
- package/dist/proxy.js +184 -0
- package/dist/proxy.js.map +1 -0
- package/dist/stt/stream.js +8 -1
- package/dist/stt/stream.js.map +1 -1
- package/package.json +3 -1
package/README.ko.md
CHANGED
|
@@ -29,6 +29,23 @@ npm install @swttch/extend-kit
|
|
|
29
29
|
|
|
30
30
|
Node.js 버전 20 이상이 필요합니다.
|
|
31
31
|
|
|
32
|
+
## 프록시 지원
|
|
33
|
+
|
|
34
|
+
프록시를 거쳐야 인터넷에 나가는 환경이라면, API 요청과 받아쓰기 모두 그 프록시를 지납니다. 읽는 환경변수는 curl과 `claude` CLI가 이미 읽는 것과 같으므로, 그 도구들이 동작하는 머신이라면 따로 설정할 것이 없습니다.
|
|
35
|
+
|
|
36
|
+
| 환경변수 | 적용 대상 |
|
|
37
|
+
|----------|-----------|
|
|
38
|
+
| `HTTPS_PROXY` / `https_proxy` | `https://` 와 `wss://` 연결 |
|
|
39
|
+
| `HTTP_PROXY` / `http_proxy` | `http://` 와 `ws://` 연결 |
|
|
40
|
+
| `ALL_PROXY` / `all_proxy` | 위 둘이 없을 때 사용 |
|
|
41
|
+
| `NO_PROXY` / `no_proxy` | 프록시를 거치지 않을 호스트. `*` 는 프록시를 통째로 끕니다 |
|
|
42
|
+
|
|
43
|
+
프록시 URL은 `http://`, `https://`, `socks://`, `socks4://`, `socks4a://`, `socks5://`, `socks5h://` 를 지원합니다. 따라서 사내 프록시뿐 아니라 `ssh -D` 로 연 터널도 그대로 동작합니다. URL에 담긴 자격증명(`http://사용자:비밀번호@호스트:포트`)은 프록시 인증에 사용됩니다.
|
|
44
|
+
|
|
45
|
+
`NO_PROXY` 는 통용되는 규칙을 따릅니다. 쉼표로 구분하고, 맨 앞의 점은 무시하며, 도메인만 적으면 그 호스트와 하위 도메인이 모두 해당됩니다. 뒤에 `:포트` 를 붙이면 그 포트에만 적용됩니다.
|
|
46
|
+
|
|
47
|
+
프록시가 아니라 게이트웨이를 통해 API에 접속한다면 `ANTHROPIC_BASE_URL` 을 설정하십시오. `claude` CLI가 읽는 환경변수와 같습니다.
|
|
48
|
+
|
|
32
49
|
## 빠른 시작
|
|
33
50
|
|
|
34
51
|
### SDK 사용
|
|
@@ -129,7 +146,7 @@ if (isTokenExpired(credentials)) {
|
|
|
129
146
|
|
|
130
147
|
API 호출을 위한 클라이언트 클래스입니다. 서브모듈을 통해 API 엔드포인트에 접근합니다.
|
|
131
148
|
|
|
132
|
-
#### `constructor(auth?: string | { apiKey: string })`
|
|
149
|
+
#### `constructor(auth?: string | { apiKey: string }, options?: ClaudeCodeClientOptions)`
|
|
133
150
|
|
|
134
151
|
OAuth 접근 토큰이나 API Key로 클라이언트를 초기화합니다. 인수를 전달하지 않으면 자동으로 크레덴셜을 읽어옵니다.
|
|
135
152
|
|
|
@@ -142,8 +159,17 @@ const client = new ClaudeCodeClient(myOAuthAccessToken);
|
|
|
142
159
|
|
|
143
160
|
// API Key
|
|
144
161
|
const client = new ClaudeCodeClient({ apiKey: process.env.ANTHROPIC_API_KEY });
|
|
162
|
+
|
|
163
|
+
// API에 직접 붙지 않고 게이트웨이를 거치는 경우
|
|
164
|
+
const client = new ClaudeCodeClient(myOAuthAccessToken, { baseUrl: 'https://gateway.internal' });
|
|
145
165
|
```
|
|
146
166
|
|
|
167
|
+
| 옵션 | 기본값 | 설명 |
|
|
168
|
+
|------|--------|------|
|
|
169
|
+
| `baseUrl` | `ANTHROPIC_BASE_URL`, 없으면 `https://api.anthropic.com` | 요청을 보낼 API 주소 |
|
|
170
|
+
|
|
171
|
+
프록시 설정은 환경변수에서 읽으므로 여기에 옵션으로 넘길 필요가 없습니다. [프록시 지원](#프록시-지원)을 참고하십시오.
|
|
172
|
+
|
|
147
173
|
#### `oauth: OAuthApi`
|
|
148
174
|
|
|
149
175
|
OAuth 관련 API를 제공하는 서브모듈입니다.
|
|
@@ -214,6 +240,8 @@ try {
|
|
|
214
240
|
| `token_expired` | OAuth 토큰이 만료됨 | `claude login` 명령 실행으로 토큰 갱신 |
|
|
215
241
|
| `api_error` | API 요청 실패 (HTTP 상태 포함) | — |
|
|
216
242
|
| `unsupported_platform` | 지원하지 않는 플랫폼 | — |
|
|
243
|
+
| `network_error` | API에 접속하지 못함 | 프록시를 거친 경우 그 사실을 알려줍니다 |
|
|
244
|
+
| `invalid_proxy` | 프록시 환경변수가 URL이 아니거나 지원하지 않는 스킴 | 기대하는 형식을 알려줍니다 |
|
|
217
245
|
|
|
218
246
|
## 타입 정의
|
|
219
247
|
|
package/README.md
CHANGED
|
@@ -29,6 +29,31 @@ npm install @swttch/extend-kit
|
|
|
29
29
|
|
|
30
30
|
Node.js version 20 or higher is required.
|
|
31
31
|
|
|
32
|
+
## Proxy Support
|
|
33
|
+
|
|
34
|
+
If your machine reaches the internet through a proxy, both the API requests and
|
|
35
|
+
dictation go through it. The variables are the ones curl and the `claude` CLI
|
|
36
|
+
already read, so a machine set up for those needs nothing extra here.
|
|
37
|
+
|
|
38
|
+
| Variable | Applies to |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| `HTTPS_PROXY` / `https_proxy` | `https://` and `wss://` connections |
|
|
41
|
+
| `HTTP_PROXY` / `http_proxy` | `http://` and `ws://` connections |
|
|
42
|
+
| `ALL_PROXY` / `all_proxy` | Used when neither of the above is set |
|
|
43
|
+
| `NO_PROXY` / `no_proxy` | Hosts to connect to directly. `*` disables the proxy entirely |
|
|
44
|
+
|
|
45
|
+
`http://`, `https://`, `socks://`, `socks4://`, `socks4a://`, `socks5://` and
|
|
46
|
+
`socks5h://` proxy URLs are supported, so an `ssh -D` tunnel works as well as a
|
|
47
|
+
corporate egress proxy. Credentials embedded in the URL
|
|
48
|
+
(`http://user:pass@host:port`) are used for proxy authentication.
|
|
49
|
+
|
|
50
|
+
`NO_PROXY` follows the usual conventions: a comma-separated list, a leading dot
|
|
51
|
+
ignored, a bare entry matching that host and its subdomains, and an optional
|
|
52
|
+
`:port` to limit the entry to one port.
|
|
53
|
+
|
|
54
|
+
If you talk to the API through a gateway rather than a proxy, set
|
|
55
|
+
`ANTHROPIC_BASE_URL` — the same variable the `claude` CLI reads.
|
|
56
|
+
|
|
32
57
|
## Quick Start
|
|
33
58
|
|
|
34
59
|
### SDK Usage
|
|
@@ -129,7 +154,7 @@ if (isTokenExpired(credentials)) {
|
|
|
129
154
|
|
|
130
155
|
A client class for making API calls. Access API endpoints through sub-modules.
|
|
131
156
|
|
|
132
|
-
#### `constructor(auth?: string | { apiKey: string })`
|
|
157
|
+
#### `constructor(auth?: string | { apiKey: string }, options?: ClaudeCodeClientOptions)`
|
|
133
158
|
|
|
134
159
|
Initializes the client with OAuth access token or API Key. If no argument is provided, credentials are resolved automatically.
|
|
135
160
|
|
|
@@ -142,8 +167,17 @@ const client = new ClaudeCodeClient(myAccessToken);
|
|
|
142
167
|
|
|
143
168
|
// API Key
|
|
144
169
|
const client = new ClaudeCodeClient({ apiKey: process.env.ANTHROPIC_API_KEY });
|
|
170
|
+
|
|
171
|
+
// Talk to a gateway instead of the API directly
|
|
172
|
+
const client = new ClaudeCodeClient(myAccessToken, { baseUrl: 'https://gateway.internal' });
|
|
145
173
|
```
|
|
146
174
|
|
|
175
|
+
| Option | Default | Description |
|
|
176
|
+
| --- | --- | --- |
|
|
177
|
+
| `baseUrl` | `ANTHROPIC_BASE_URL`, else `https://api.anthropic.com` | API origin to send requests to |
|
|
178
|
+
|
|
179
|
+
Proxy settings are read from the environment and need no option here — see [Proxy Support](#proxy-support).
|
|
180
|
+
|
|
147
181
|
#### `oauth: OAuthApi`
|
|
148
182
|
|
|
149
183
|
A sub-module that provides OAuth-related API methods.
|
|
@@ -214,6 +248,8 @@ try {
|
|
|
214
248
|
| `token_expired` | OAuth token has expired | Run `claude login` to refresh your token |
|
|
215
249
|
| `api_error` | API request failed (includes HTTP status) | — |
|
|
216
250
|
| `unsupported_platform` | Platform not supported | — |
|
|
251
|
+
| `network_error` | The API could not be reached | Names the proxy when one was in use |
|
|
252
|
+
| `invalid_proxy` | A proxy variable is not a URL, or its scheme is not supported | Shows the expected form |
|
|
217
253
|
|
|
218
254
|
## Type Definitions
|
|
219
255
|
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { OAuthApi } from './oauth.js';
|
|
2
2
|
import type { ClientAuth } from '../auth/index.js';
|
|
3
|
+
/** Options that change where or how requests go out. */
|
|
4
|
+
export interface ClaudeCodeClientOptions {
|
|
5
|
+
/**
|
|
6
|
+
* API origin to talk to. Defaults to ANTHROPIC_BASE_URL when set, and to the
|
|
7
|
+
* real API otherwise.
|
|
8
|
+
*/
|
|
9
|
+
baseUrl?: string;
|
|
10
|
+
}
|
|
3
11
|
export declare class ClaudeCodeClient {
|
|
4
12
|
private auth?;
|
|
5
13
|
private accessToken?;
|
|
6
14
|
private _oauth?;
|
|
7
|
-
|
|
15
|
+
private readonly baseUrl;
|
|
16
|
+
constructor(auth?: ClientAuth, options?: ClaudeCodeClientOptions);
|
|
8
17
|
get isApiKeyMode(): boolean;
|
|
9
18
|
get oauth(): OAuthApi;
|
|
10
19
|
private resolveToken;
|
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
import { OAuthApi } from './oauth.js';
|
|
2
|
+
import { getJson, proxyInUseFor } from './http.js';
|
|
2
3
|
import { getCredentials, getAccessToken, isApiKeyAuth } from '../auth/index.js';
|
|
3
4
|
import { CcbError } from '../errors.js';
|
|
4
5
|
const BASE_URL = 'https://api.anthropic.com';
|
|
6
|
+
/**
|
|
7
|
+
* The origin to use, with any trailing slash removed so that joining a path that
|
|
8
|
+
* starts with "/" cannot produce a double slash.
|
|
9
|
+
*
|
|
10
|
+
* ANTHROPIC_BASE_URL is honored because the `claude` CLI honors it: a user
|
|
11
|
+
* behind a corporate gateway points both at the same place, and `ccb` claiming
|
|
12
|
+
* not to know the setting the rest of their toolchain uses would be a gap for no
|
|
13
|
+
* reason. It also lets the tests here talk to a local server over the real
|
|
14
|
+
* request path instead of stubbing the transport.
|
|
15
|
+
*/
|
|
16
|
+
function resolveBaseUrl(explicit) {
|
|
17
|
+
const chosen = explicit ?? process.env.ANTHROPIC_BASE_URL?.trim() ?? '';
|
|
18
|
+
return (chosen || BASE_URL).replace(/\/+$/, '');
|
|
19
|
+
}
|
|
5
20
|
export class ClaudeCodeClient {
|
|
6
21
|
auth;
|
|
7
22
|
accessToken;
|
|
8
23
|
_oauth;
|
|
9
|
-
|
|
24
|
+
baseUrl;
|
|
25
|
+
constructor(auth, options = {}) {
|
|
10
26
|
this.auth = auth;
|
|
27
|
+
this.baseUrl = resolveBaseUrl(options.baseUrl);
|
|
11
28
|
if (typeof auth === 'string') {
|
|
12
29
|
this.accessToken = auth;
|
|
13
30
|
}
|
|
@@ -41,14 +58,61 @@ export class ClaudeCodeClient {
|
|
|
41
58
|
...headers,
|
|
42
59
|
};
|
|
43
60
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
61
|
+
// getJson rather than fetch: fetch takes no `http.Agent`, and `ws` proxies
|
|
62
|
+
// only when handed one, so a single proxy implementation has to be built on
|
|
63
|
+
// something both transports accept. See src/proxy.ts.
|
|
64
|
+
const url = `${this.baseUrl}${path}`;
|
|
65
|
+
const response = await getJson(url, requestHeaders);
|
|
48
66
|
if (!response.ok) {
|
|
49
|
-
throw
|
|
67
|
+
throw describeApiFailure(response, url);
|
|
50
68
|
}
|
|
51
|
-
return response.
|
|
69
|
+
return response.body;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Turn an HTTP status into an error a caller can route on and a person can act on.
|
|
74
|
+
*
|
|
75
|
+
* Every non-2xx used to become one `api_error` reading "API error 403: Forbidden",
|
|
76
|
+
* which is the same sentence for an expired login, a rate limit, and a request the
|
|
77
|
+
* gateway blocked. Nothing downstream could tell them apart, so the plugin's usage
|
|
78
|
+
* panel classified all of them as an authentication problem and told people to log
|
|
79
|
+
* in again no matter what had actually happened.
|
|
80
|
+
*
|
|
81
|
+
* The proxy is named whenever one is in play. A refusal that reaches this function
|
|
82
|
+
* came from the destination — {@link getJson} throws before this point when the
|
|
83
|
+
* proxy itself refused the tunnel — but knowing which route the request took is
|
|
84
|
+
* what makes the difference checkable rather than guessable.
|
|
85
|
+
*/
|
|
86
|
+
function describeApiFailure(response, url) {
|
|
87
|
+
const { status, statusText, retryAfterSec } = response;
|
|
88
|
+
const message = `API error ${status}: ${statusText}`;
|
|
89
|
+
const proxyUrl = proxyInUseFor(url);
|
|
90
|
+
const route = proxyUrl
|
|
91
|
+
? `The request reached the API through the proxy at ${proxyUrl}.`
|
|
92
|
+
: 'The request went straight to the API, with no proxy configured for this process.';
|
|
93
|
+
// Repeated on every branch so a caller never has to know which code carries
|
|
94
|
+
// which facts. `reachedDestination` is true throughout: getJson throws before
|
|
95
|
+
// this point when a proxy refused the tunnel, so anything here was answered by
|
|
96
|
+
// the destination itself.
|
|
97
|
+
const details = { status, ...(proxyUrl && { proxyUrl }), reachedDestination: true };
|
|
98
|
+
if (status === 401) {
|
|
99
|
+
return new CcbError(message, 'token_expired', 'The saved login is no longer accepted. Run `claude` once to refresh it.', details);
|
|
100
|
+
}
|
|
101
|
+
if (status === 403) {
|
|
102
|
+
// The case this whole taxonomy came out of: a machine that can only reach
|
|
103
|
+
// Anthropic through a proxy, with the request going direct because the proxy
|
|
104
|
+
// was configured under a variable this tool was not reading. The gateway
|
|
105
|
+
// answers 403, which looks like an account problem and is not one.
|
|
106
|
+
return new CcbError(message, 'forbidden', `${route} A 403 here is usually the network refusing the request rather than the account lacking access.`, details);
|
|
107
|
+
}
|
|
108
|
+
if (status === 429) {
|
|
109
|
+
return new CcbError(message, 'rate_limited', retryAfterSec !== undefined
|
|
110
|
+
? `Too many requests. The API asked us to wait ${retryAfterSec}s.`
|
|
111
|
+
: 'Too many requests in a short window. Try again shortly.', { ...details, ...(retryAfterSec !== undefined && { retryAfterSec }) });
|
|
112
|
+
}
|
|
113
|
+
if (status >= 500) {
|
|
114
|
+
return new CcbError(message, 'server_error', `${route} The API itself reported an error.`, details);
|
|
52
115
|
}
|
|
116
|
+
return new CcbError(message, 'api_error', route, details);
|
|
53
117
|
}
|
|
54
118
|
//# sourceMappingURL=client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/battery/api/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhF,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,MAAM,QAAQ,GAAG,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/battery/api/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type Agent } from 'node:http';
|
|
2
|
+
/** What a caller needs to know about the response, mirroring the shape `fetch` returns. */
|
|
3
|
+
export interface JsonResponse<T> {
|
|
4
|
+
ok: boolean;
|
|
5
|
+
status: number;
|
|
6
|
+
statusText: string;
|
|
7
|
+
body: T;
|
|
8
|
+
/**
|
|
9
|
+
* Seconds the destination asked us to wait, parsed from Retry-After.
|
|
10
|
+
*
|
|
11
|
+
* Surfaced as a number because the only alternative is for a caller to find it
|
|
12
|
+
* by matching prose, and a caller that reads prose breaks when the prose changes.
|
|
13
|
+
* Absent when the header was missing or was an HTTP-date rather than a count.
|
|
14
|
+
*/
|
|
15
|
+
retryAfterSec?: number;
|
|
16
|
+
}
|
|
17
|
+
/** Options that change how a request is made, rather than what is requested. */
|
|
18
|
+
export interface GetJsonOptions {
|
|
19
|
+
/** Use this agent instead of resolving one from the proxy environment. */
|
|
20
|
+
agent?: Agent;
|
|
21
|
+
/** Total budget for the request, covering connect, tunnel, and response. */
|
|
22
|
+
deadlineMs?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* GET a JSON document, through a proxy when one is configured.
|
|
26
|
+
*
|
|
27
|
+
* A dead or misconfigured proxy fails at connect time, and the raw errno that
|
|
28
|
+
* surfaces ("ECONNREFUSED 127.0.0.1:8080") names the proxy without saying that a
|
|
29
|
+
* proxy is what failed. Since the proxy is invisible in the URL the user asked
|
|
30
|
+
* for, the error says so explicitly.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getJson<T>(url: string, headers: Record<string, string>, options?: GetJsonOptions): Promise<JsonResponse<T>>;
|
|
33
|
+
/**
|
|
34
|
+
* The proxy in play for `url`, safe to print, so a caller can name it in its own
|
|
35
|
+
* errors. Credentials are stripped here rather than at each call site, because a
|
|
36
|
+
* call site that forgets is a call site that prints a password.
|
|
37
|
+
*/
|
|
38
|
+
export declare function proxyInUseFor(url: string): string | undefined;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HTTP transport for everything this package fetches.
|
|
3
|
+
*
|
|
4
|
+
* Global `fetch` cannot be handed an `http.Agent`, and `ws` proxies only when it
|
|
5
|
+
* is handed one, so routing both transports through a single proxy
|
|
6
|
+
* implementation means using `node:https` here. Proxying `fetch` itself is
|
|
7
|
+
* possible by other means — undici's ProxyAgent with setGlobalDispatcher — but a
|
|
8
|
+
* dispatcher does not cover `ws`, which is why that route was not taken.
|
|
9
|
+
* See {@link proxyAgentFor}.
|
|
10
|
+
*/
|
|
11
|
+
import { request as httpsRequest } from 'node:https';
|
|
12
|
+
import { request as httpRequest } from 'node:http';
|
|
13
|
+
import { proxyAgentFor, resolveProxyUrl, redactProxyUrl } from '../../proxy.js';
|
|
14
|
+
import { CcbError } from '../errors.js';
|
|
15
|
+
/** Retry-After is either a count of seconds or an HTTP-date; only the former is useful here. */
|
|
16
|
+
function parseRetryAfter(value) {
|
|
17
|
+
const raw = Array.isArray(value) ? value[0] : value;
|
|
18
|
+
if (!raw)
|
|
19
|
+
return undefined;
|
|
20
|
+
const seconds = Number(raw.trim());
|
|
21
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
22
|
+
return seconds;
|
|
23
|
+
const at = Date.parse(raw);
|
|
24
|
+
if (Number.isNaN(at))
|
|
25
|
+
return undefined;
|
|
26
|
+
return Math.max(0, Math.round((at - Date.now()) / 1000));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* How long a request may take in total: connecting, opening the tunnel when a
|
|
30
|
+
* proxy is in the way, and reading the response.
|
|
31
|
+
*
|
|
32
|
+
* One budget rather than a socket timeout, because a socket timeout starts when
|
|
33
|
+
* there is a socket. A proxy that is slow to answer CONNECT has not produced one
|
|
34
|
+
* yet, so the guard that was here before did not cover the very case its comment
|
|
35
|
+
* described: a proxy asked to open a tunnel and left to think about it for 70
|
|
36
|
+
* seconds ran to completion, unguarded, while the same 70-second silence after
|
|
37
|
+
* the tunnel opened was cut off at 60. Measured, not reasoned about.
|
|
38
|
+
*/
|
|
39
|
+
const DEFAULT_DEADLINE_MS = 60_000;
|
|
40
|
+
/**
|
|
41
|
+
* How long a request may take in total, before the caller's own option.
|
|
42
|
+
*
|
|
43
|
+
* A spawner with a shorter budget than ours can say so here. The plugin backend
|
|
44
|
+
* gives `ccb` 15 seconds and kills the process at that mark, which produces a
|
|
45
|
+
* dead child and whatever its login shell had printed by then rather than an
|
|
46
|
+
* explanation — the user was shown `(eval):1: can't change option: zle`. Told the
|
|
47
|
+
* budget, `ccb` finishes first and says what actually went wrong.
|
|
48
|
+
*/
|
|
49
|
+
function defaultDeadlineMs() {
|
|
50
|
+
const raw = Number(process.env.CCB_REQUEST_TIMEOUT_MS);
|
|
51
|
+
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_DEADLINE_MS;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* GET a JSON document, through a proxy when one is configured.
|
|
55
|
+
*
|
|
56
|
+
* A dead or misconfigured proxy fails at connect time, and the raw errno that
|
|
57
|
+
* surfaces ("ECONNREFUSED 127.0.0.1:8080") names the proxy without saying that a
|
|
58
|
+
* proxy is what failed. Since the proxy is invisible in the URL the user asked
|
|
59
|
+
* for, the error says so explicitly.
|
|
60
|
+
*/
|
|
61
|
+
export async function getJson(url, headers, options = {}) {
|
|
62
|
+
const { agent: agentOverride, deadlineMs = defaultDeadlineMs() } = options;
|
|
63
|
+
// Built before the agent, because the agent needs it: see the note in
|
|
64
|
+
// proxyAgentFor on why a signal given only to the request cannot end a wait
|
|
65
|
+
// that has not produced a socket yet.
|
|
66
|
+
const abort = new AbortController();
|
|
67
|
+
const agent = agentOverride
|
|
68
|
+
?? proxyAgentFor(url, process.env, { signal: abort.signal, timeoutMs: deadlineMs });
|
|
69
|
+
const viaProxy = agent !== undefined;
|
|
70
|
+
// Only meaningful for the agent this function built: an override comes from a
|
|
71
|
+
// caller that knows its own routing, and naming an environment proxy it is not
|
|
72
|
+
// using would be worse than saying nothing.
|
|
73
|
+
const proxyUrl = agentOverride ? undefined : redactProxyUrl(resolveProxyUrl(url));
|
|
74
|
+
// The API is always https; plain http is here so tests can run against a local
|
|
75
|
+
// server without a certificate, and it costs one branch.
|
|
76
|
+
const request = new URL(url).protocol === 'http:' ? httpRequest : httpsRequest;
|
|
77
|
+
// Aborting is what actually stops the work, rather than only telling the caller
|
|
78
|
+
// to stop waiting for it. One signal covers both halves: the agent destroys the
|
|
79
|
+
// socket it is reading the CONNECT response from, and the request tears down a
|
|
80
|
+
// socket it has already been handed.
|
|
81
|
+
let socketToKill;
|
|
82
|
+
let phase = 'connecting';
|
|
83
|
+
let proxyConnectStatus;
|
|
84
|
+
let timedOut = false;
|
|
85
|
+
const deadline = setTimeout(() => {
|
|
86
|
+
timedOut = true;
|
|
87
|
+
abort.abort();
|
|
88
|
+
socketToKill?.destroy();
|
|
89
|
+
}, deadlineMs);
|
|
90
|
+
const describeTimeout = () => new CcbError(`Request to ${url} timed out after ${Math.round(deadlineMs / 1000)}s`, 'timeout', phase === 'connecting' && viaProxy
|
|
91
|
+
? `The proxy${proxyUrl ? ` at ${proxyUrl}` : ''} never finished opening the tunnel.`
|
|
92
|
+
: viaProxy
|
|
93
|
+
? 'The tunnel was open, but the destination never replied.'
|
|
94
|
+
: undefined, { ...(proxyUrl && { proxyUrl }), reachedDestination: phase === 'tunnelled' });
|
|
95
|
+
let result;
|
|
96
|
+
try {
|
|
97
|
+
result = await new Promise((resolve, reject) => {
|
|
98
|
+
const req = request(url, { method: 'GET', headers, agent, signal: abort.signal }, (res) => {
|
|
99
|
+
const chunks = [];
|
|
100
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
101
|
+
res.on('end', () => resolve({
|
|
102
|
+
status: res.statusCode ?? 0,
|
|
103
|
+
statusText: res.statusMessage ?? '',
|
|
104
|
+
text: Buffer.concat(chunks).toString('utf-8'),
|
|
105
|
+
retryAfterSec: parseRetryAfter(res.headers['retry-after']),
|
|
106
|
+
}));
|
|
107
|
+
res.on('error', reject);
|
|
108
|
+
});
|
|
109
|
+
// https-proxy-agent announces the CONNECT response here before it decides
|
|
110
|
+
// what to do with it. Recording the status is what lets a refusal be told
|
|
111
|
+
// apart from an answer by the destination — see the throw below.
|
|
112
|
+
req.on('proxyConnect', (connect) => {
|
|
113
|
+
proxyConnectStatus = connect.statusCode;
|
|
114
|
+
if (connect.statusCode === 200)
|
|
115
|
+
phase = 'tunnelled';
|
|
116
|
+
});
|
|
117
|
+
req.on('socket', (socket) => {
|
|
118
|
+
socketToKill = socket;
|
|
119
|
+
// Without a proxy there is no CONNECT step to leave behind: once the
|
|
120
|
+
// socket exists, any further wait is the destination's.
|
|
121
|
+
if (!viaProxy)
|
|
122
|
+
phase = 'tunnelled';
|
|
123
|
+
});
|
|
124
|
+
req.on('error', (err) => {
|
|
125
|
+
if (timedOut)
|
|
126
|
+
return reject(describeTimeout());
|
|
127
|
+
if (err instanceof CcbError)
|
|
128
|
+
return reject(err);
|
|
129
|
+
reject(new CcbError(`Could not reach ${new URL(url).host}: ${err.message}`, 'network_error', viaProxy
|
|
130
|
+
? 'This connection went through the proxy from HTTP_PROXY/HTTPS_PROXY/ALL_PROXY. Check that the proxy is running and reachable.'
|
|
131
|
+
: undefined, { ...(proxyUrl && { proxyUrl }), reachedDestination: false }));
|
|
132
|
+
});
|
|
133
|
+
req.end();
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
clearTimeout(deadline);
|
|
138
|
+
}
|
|
139
|
+
// A proxy that refuses the CONNECT does not fail the request. https-proxy-agent
|
|
140
|
+
// replays the refusal onto a stand-in socket so node's HTTP parser reads it,
|
|
141
|
+
// which means the proxy's own "403 Forbidden" arrives here shaped exactly like a
|
|
142
|
+
// 403 from the API.
|
|
143
|
+
//
|
|
144
|
+
// That replay is deliberate on the library's part, not something to route around:
|
|
145
|
+
// it destroys the real socket first so the request — Authorization header and all
|
|
146
|
+
// — is never written to a proxy that just declined to carry it (HackerOne
|
|
147
|
+
// #541502). The status is therefore the only thing that survives, and it survives
|
|
148
|
+
// wearing the destination's clothes. Reporting it as an API error sends the user
|
|
149
|
+
// to look at their account when the answer is their proxy, so the two are told
|
|
150
|
+
// apart here rather than left for a person to guess at.
|
|
151
|
+
//
|
|
152
|
+
// Verified against https-proxy-agent 9.1.0 (dist/index.js:104, 134-157): the
|
|
153
|
+
// proxyConnect event fires before the stand-in socket is built.
|
|
154
|
+
if (proxyConnectStatus !== undefined && proxyConnectStatus !== 200) {
|
|
155
|
+
throw new CcbError(`The proxy refused to open a tunnel to ${new URL(url).host} (${proxyConnectStatus} ${result.statusText})`, 'proxy_rejected', proxyConnectStatus === 407
|
|
156
|
+
? `The proxy${proxyUrl ? ` at ${proxyUrl}` : ''} requires authentication. Put the credentials in the proxy URL, as in http://user:pass@host:port.`
|
|
157
|
+
: `This is the proxy${proxyUrl ? ` at ${proxyUrl}` : ''} answering, not the Anthropic API.`, { ...(proxyUrl && { proxyUrl }), proxyConnectStatus, reachedDestination: false });
|
|
158
|
+
}
|
|
159
|
+
const { status, statusText, text, retryAfterSec } = result;
|
|
160
|
+
if (status < 200 || status >= 300) {
|
|
161
|
+
return { ok: false, status, statusText, body: undefined, ...(retryAfterSec !== undefined && { retryAfterSec }) };
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
return { ok: true, status, statusText, body: JSON.parse(text) };
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
throw new CcbError(`Expected JSON from ${url} but got something else`, 'api_error', viaProxy ? 'A proxy that returns an error page instead of the response looks like this.' : undefined, { status, ...(proxyUrl && { proxyUrl }), reachedDestination: true });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* The proxy in play for `url`, safe to print, so a caller can name it in its own
|
|
172
|
+
* errors. Credentials are stripped here rather than at each call site, because a
|
|
173
|
+
* call site that forgets is a call site that prints a password.
|
|
174
|
+
*/
|
|
175
|
+
export function proxyInUseFor(url) {
|
|
176
|
+
return redactProxyUrl(resolveProxyUrl(url));
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/battery/api/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { ClaudeCodeClient } from './client.js';
|
|
2
|
+
export type { ClaudeCodeClientOptions } from './client.js';
|
|
2
3
|
export { OAuthApi } from './oauth.js';
|
|
3
|
-
export type { UsageResponse, UsageBucket, ExtraUsage, ProfileResponse, AccountInfo, OrganizationInfo, ApplicationInfo, } from './types.js';
|
|
4
|
+
export type { UsageResponse, UsageBucket, UsageLimit, ExtraUsage, ProfileResponse, AccountInfo, OrganizationInfo, ApplicationInfo, } from './types.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/battery/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/battery/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -1,13 +1,55 @@
|
|
|
1
1
|
export interface UsageBucket {
|
|
2
2
|
utilization: number;
|
|
3
3
|
resets_at: string | null;
|
|
4
|
+
limit_dollars?: number | null;
|
|
5
|
+
used_dollars?: number | null;
|
|
6
|
+
remaining_dollars?: number | null;
|
|
7
|
+
locked_reason?: string | null;
|
|
4
8
|
}
|
|
5
9
|
export interface ExtraUsage {
|
|
6
10
|
is_enabled: boolean;
|
|
7
11
|
monthly_limit: number | null;
|
|
8
12
|
used_credits: number | null;
|
|
9
13
|
utilization: number | null;
|
|
14
|
+
currency?: string | null;
|
|
15
|
+
decimal_places?: number | null;
|
|
16
|
+
disabled_reason?: string | null;
|
|
17
|
+
user_disabled?: boolean;
|
|
18
|
+
spend_limit_reached?: boolean;
|
|
10
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* A limit the API describes by name rather than by giving it a top-level key.
|
|
22
|
+
*
|
|
23
|
+
* Newer per-model windows arrive only in the `limits` array, so a reader that
|
|
24
|
+
* walks the top-level keys never sees them. That is why a Fable weekly limit is
|
|
25
|
+
* absent from the usage display while the API is plainly reporting it: the array
|
|
26
|
+
* is not a duplicate of the flat fields, it is where the entries the flat fields
|
|
27
|
+
* have no name for live.
|
|
28
|
+
*/
|
|
29
|
+
export interface UsageLimit {
|
|
30
|
+
kind: string;
|
|
31
|
+
percent: number;
|
|
32
|
+
resets_at?: string | null;
|
|
33
|
+
scope?: {
|
|
34
|
+
model?: {
|
|
35
|
+
display_name?: string;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
};
|
|
38
|
+
[key: string]: unknown;
|
|
39
|
+
};
|
|
40
|
+
[key: string]: unknown;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The usage payload, as far as it is worth naming.
|
|
44
|
+
*
|
|
45
|
+
* The index signature is deliberate. Anthropic ships new windows under
|
|
46
|
+
* codenames (`nimbus_quill`, `juniper_tide`, `tangelo`) without warning, and a
|
|
47
|
+
* closed type would mean cutting a release of this package every time one
|
|
48
|
+
* appears. More importantly, a closed type reads as a promise that these are all
|
|
49
|
+
* the fields there are, and a reader that believed that promise is exactly how
|
|
50
|
+
* the `limits` array came to be overlooked. The runtime payload was never
|
|
51
|
+
* trimmed; only the type claimed it was complete.
|
|
52
|
+
*/
|
|
11
53
|
export interface UsageResponse {
|
|
12
54
|
five_hour: UsageBucket | null;
|
|
13
55
|
seven_day: UsageBucket | null;
|
|
@@ -17,6 +59,8 @@ export interface UsageResponse {
|
|
|
17
59
|
seven_day_cowork: UsageBucket | null;
|
|
18
60
|
iguana_necktie: unknown | null;
|
|
19
61
|
extra_usage: ExtraUsage | null;
|
|
62
|
+
limits?: UsageLimit[] | null;
|
|
63
|
+
[key: string]: unknown;
|
|
20
64
|
}
|
|
21
65
|
export interface AccountInfo {
|
|
22
66
|
uuid: string;
|
package/dist/battery/errors.d.ts
CHANGED
|
@@ -1,9 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Facts a caller may need to act on, carried as fields rather than folded into
|
|
3
|
+
* the message.
|
|
4
|
+
*
|
|
5
|
+
* A caller that has to recover the retry delay by matching a regular expression
|
|
6
|
+
* against prose is a caller that breaks the next time the prose is reworded. The
|
|
7
|
+
* plugin backend does exactly that today, and every one of these fields exists
|
|
8
|
+
* because something downstream would otherwise have to read the sentence.
|
|
9
|
+
*/
|
|
10
|
+
export interface CcbErrorDetails {
|
|
11
|
+
/** HTTP status the destination answered with, when there was one. */
|
|
12
|
+
status?: number;
|
|
13
|
+
/** How long to wait before trying again, from a Retry-After header. */
|
|
14
|
+
retryAfterSec?: number;
|
|
15
|
+
/** The proxy the request was routed through, when one applied. */
|
|
16
|
+
proxyUrl?: string;
|
|
17
|
+
/** The status a proxy answered a CONNECT with, when it refused the tunnel. */
|
|
18
|
+
proxyConnectStatus?: number;
|
|
19
|
+
/** Whether the request reached the destination, or stalled before the tunnel opened. */
|
|
20
|
+
reachedDestination?: boolean;
|
|
21
|
+
}
|
|
1
22
|
export declare class CcbError extends Error {
|
|
2
23
|
readonly code: string;
|
|
3
24
|
readonly hint?: string | undefined;
|
|
4
|
-
|
|
25
|
+
readonly details?: CcbErrorDetails | undefined;
|
|
26
|
+
constructor(message: string, code: string, hint?: string | undefined, details?: CcbErrorDetails | undefined);
|
|
5
27
|
toJSON(): {
|
|
6
28
|
error: {
|
|
29
|
+
details?: CcbErrorDetails | undefined;
|
|
7
30
|
hint?: string | undefined;
|
|
8
31
|
code: string;
|
|
9
32
|
message: string;
|
package/dist/battery/errors.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export class CcbError extends Error {
|
|
2
2
|
code;
|
|
3
3
|
hint;
|
|
4
|
-
|
|
4
|
+
details;
|
|
5
|
+
constructor(message, code, hint, details) {
|
|
5
6
|
super(message);
|
|
6
7
|
this.code = code;
|
|
7
8
|
this.hint = hint;
|
|
9
|
+
this.details = details;
|
|
8
10
|
this.name = 'CcbError';
|
|
9
11
|
}
|
|
10
12
|
toJSON() {
|
|
@@ -13,6 +15,7 @@ export class CcbError extends Error {
|
|
|
13
15
|
code: this.code,
|
|
14
16
|
message: this.message,
|
|
15
17
|
...(this.hint && { hint: this.hint }),
|
|
18
|
+
...(this.details && Object.keys(this.details).length > 0 && { details: this.details }),
|
|
16
19
|
},
|
|
17
20
|
};
|
|
18
21
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/battery/errors.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/battery/errors.ts"],"names":[],"mappings":"AAsBA,MAAM,OAAO,QAAS,SAAQ,KAAK;IAGf;IACA;IACA;IAJlB,YACE,OAAe,EACC,IAAY,EACZ,IAAa,EACb,OAAyB;QAEzC,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,SAAI,GAAJ,IAAI,CAAQ;QACZ,SAAI,GAAJ,IAAI,CAAS;QACb,YAAO,GAAP,OAAO,CAAkB;QAGzC,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IACzB,CAAC;IAED,MAAM;QACJ,OAAO;YACL,KAAK,EAAE;gBACL,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;gBACrC,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;aACvF;SACF,CAAC;IACJ,CAAC;CACF"}
|
package/dist/battery/index.d.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* user's own machine and their own login.
|
|
9
9
|
*/
|
|
10
10
|
export { CcbError } from './errors.js';
|
|
11
|
+
export type { CcbErrorDetails } from './errors.js';
|
|
11
12
|
export { getCredentials, getAccessToken, isTokenExpired, isApiKeyAuth } from './auth/index.js';
|
|
12
13
|
export type { ClaudeCredentials, ClaudeOAuthCredentials, ApiKeyAuth, ClientAuth } from './auth/index.js';
|
|
13
14
|
export { ClaudeCodeClient, OAuthApi } from './api/index.js';
|
|
14
|
-
export type { UsageResponse, UsageBucket, ExtraUsage, ProfileResponse, AccountInfo, OrganizationInfo, ApplicationInfo, } from './api/index.js';
|
|
15
|
+
export type { UsageResponse, UsageBucket, UsageLimit, ExtraUsage, ProfileResponse, AccountInfo, OrganizationInfo, ApplicationInfo, } from './api/index.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/battery/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/battery/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAG/F,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/proxy.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Agent } from 'node:http';
|
|
2
|
+
/**
|
|
3
|
+
* A proxy URL with any username and password taken out, safe to show a person.
|
|
4
|
+
*
|
|
5
|
+
* Proxy credentials live in the URL itself, and the URL is the most useful thing
|
|
6
|
+
* an error about a proxy can name. Those two facts together are how a password
|
|
7
|
+
* ends up in an error message, and from there in the screenshot someone attaches
|
|
8
|
+
* to a bug report. Every message this package writes goes through here first.
|
|
9
|
+
*
|
|
10
|
+
* The reporter on Swttch/swttch#181 configured exactly such a URL, so this is not
|
|
11
|
+
* a hypothetical shape.
|
|
12
|
+
*/
|
|
13
|
+
export declare function redactProxyUrl(proxyUrl: string | undefined): string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Whether NO_PROXY exempts this host, following the de-facto rules curl uses:
|
|
16
|
+
* a comma-separated list, `*` meaning everything, a leading dot ignored, and a
|
|
17
|
+
* bare entry matching that host plus its subdomains. An entry may pin a port,
|
|
18
|
+
* in which case the port must match too.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isProxyBypassed(host: string, port: string, noProxy: string | undefined): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* The proxy URL that applies to `targetUrl`, or undefined for a direct connection.
|
|
23
|
+
*
|
|
24
|
+
* `wss:`/`ws:` are resolved as their HTTP equivalents because that is how the
|
|
25
|
+
* connection is actually made — a WebSocket starts life as an HTTP upgrade, so
|
|
26
|
+
* a user who set HTTPS_PROXY expects `wss://` to go through it.
|
|
27
|
+
*
|
|
28
|
+
* **An https target falls back to HTTP_PROXY, and that is a deliberate departure
|
|
29
|
+
* from curl.** curl reads only the variable matching the target's scheme, so
|
|
30
|
+
* HTTP_PROXY never applies to an https URL there. The `claude` CLI does not
|
|
31
|
+
* follow that rule: given HTTP_PROXY alone, whether exported in the environment
|
|
32
|
+
* or written into settings.json's `env` block, it tunnels api.anthropic.com
|
|
33
|
+
* through it. Both behaviours were measured against a local CONNECT proxy.
|
|
34
|
+
*
|
|
35
|
+
* Matching curl instead of `claude` is what broke Swttch/swttch#432 for the
|
|
36
|
+
* person who reported it: one settings.json, chat working because the CLI
|
|
37
|
+
* honored HTTP_PROXY, and the usage panel going out direct because this function
|
|
38
|
+
* did not. The user writes that file for `claude`, so `claude` is the behaviour
|
|
39
|
+
* to match — the plugin's whole premise is that anything possible from the CLI
|
|
40
|
+
* is possible from the GUI.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveProxyUrl(targetUrl: string, env?: NodeJS.ProcessEnv): string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* An agent that routes a connection to `targetUrl` through the configured proxy,
|
|
45
|
+
* or undefined when no proxy applies and the connection should go out directly.
|
|
46
|
+
*
|
|
47
|
+
* The result is an `http.Agent`, which is the one shape both callers accept:
|
|
48
|
+
* `https.request` takes it as `agent`, and `ws` takes the same object under the
|
|
49
|
+
* same name. That is why this package talks to the API over `node:https` rather
|
|
50
|
+
* than global `fetch` — `fetch` cannot be handed one of these, so using it would
|
|
51
|
+
* mean two separate proxy implementations for the two transports.
|
|
52
|
+
*
|
|
53
|
+
* A malformed proxy URL throws rather than falling back to a direct connection.
|
|
54
|
+
* On a machine that can only reach the internet through a proxy the direct
|
|
55
|
+
* attempt fails anyway, and it fails with a connection error that says nothing
|
|
56
|
+
* about the real cause; naming the bad setting is far more useful.
|
|
57
|
+
*/
|
|
58
|
+
export declare function proxyAgentFor(targetUrl: string, env?: NodeJS.ProcessEnv, options?: {
|
|
59
|
+
signal?: AbortSignal;
|
|
60
|
+
timeoutMs?: number;
|
|
61
|
+
}): Agent | undefined;
|
package/dist/proxy.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
2
|
+
import { SocksProxyAgent } from 'socks-proxy-agent';
|
|
3
|
+
import { CcbError } from './battery/errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* Proxy support for every outbound connection this package makes.
|
|
6
|
+
*
|
|
7
|
+
* Node does not route outbound requests through a proxy on its own. Its global
|
|
8
|
+
* `fetch` ignores HTTP_PROXY entirely (Node 24 added an opt-in flag for it, but
|
|
9
|
+
* this package supports Node 20+, and that flag does not cover SOCKS at all), and
|
|
10
|
+
* `ws` only proxies when handed an agent. So a user whose machine reaches the
|
|
11
|
+
* internet only through a proxy — a corporate egress proxy, or an `ssh -D`
|
|
12
|
+
* tunnel — got a plain connection failure from `ccb`, while `claude` itself
|
|
13
|
+
* worked because the CLI does its own proxying. That gap is what this module
|
|
14
|
+
* closes: see Swttch/swttch#181.
|
|
15
|
+
*
|
|
16
|
+
* The variables read here are the ones curl and the claude CLI already read, so
|
|
17
|
+
* a machine that is set up for those needs no extra configuration for `ccb`.
|
|
18
|
+
*/
|
|
19
|
+
/** Proxy URL schemes that mean SOCKS rather than an HTTP CONNECT proxy. */
|
|
20
|
+
const SOCKS_PROTOCOLS = new Set(['socks:', 'socks4:', 'socks4a:', 'socks5:', 'socks5h:']);
|
|
21
|
+
/**
|
|
22
|
+
* A proxy URL with any username and password taken out, safe to show a person.
|
|
23
|
+
*
|
|
24
|
+
* Proxy credentials live in the URL itself, and the URL is the most useful thing
|
|
25
|
+
* an error about a proxy can name. Those two facts together are how a password
|
|
26
|
+
* ends up in an error message, and from there in the screenshot someone attaches
|
|
27
|
+
* to a bug report. Every message this package writes goes through here first.
|
|
28
|
+
*
|
|
29
|
+
* The reporter on Swttch/swttch#181 configured exactly such a URL, so this is not
|
|
30
|
+
* a hypothetical shape.
|
|
31
|
+
*/
|
|
32
|
+
export function redactProxyUrl(proxyUrl) {
|
|
33
|
+
if (!proxyUrl)
|
|
34
|
+
return undefined;
|
|
35
|
+
try {
|
|
36
|
+
const url = new URL(proxyUrl);
|
|
37
|
+
if (!url.username && !url.password)
|
|
38
|
+
return proxyUrl;
|
|
39
|
+
url.username = '';
|
|
40
|
+
url.password = '';
|
|
41
|
+
// A URL that carried credentials should still look like it did, so the user
|
|
42
|
+
// recognises which setting is being talked about.
|
|
43
|
+
return url.toString().replace('//', '//***@');
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Not parseable, so it cannot be picked apart safely. Say nothing rather than
|
|
47
|
+
// risk printing a password that happens to sit in an unparseable string.
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Read a proxy variable in either spelling.
|
|
53
|
+
*
|
|
54
|
+
* Both cases are in real use — most tooling documents the uppercase form, while
|
|
55
|
+
* curl has historically honored the lowercase one — and users copy whichever
|
|
56
|
+
* their last set of instructions showed. Reading only one spelling silently
|
|
57
|
+
* ignores a proxy the user believes they configured, which is the whole failure
|
|
58
|
+
* this module exists to prevent, so both are accepted with uppercase winning a
|
|
59
|
+
* conflict.
|
|
60
|
+
*/
|
|
61
|
+
function readProxyEnv(env, name) {
|
|
62
|
+
const upper = env[name.toUpperCase()];
|
|
63
|
+
if (typeof upper === 'string' && upper.trim())
|
|
64
|
+
return upper.trim();
|
|
65
|
+
const lower = env[name.toLowerCase()];
|
|
66
|
+
if (typeof lower === 'string' && lower.trim())
|
|
67
|
+
return lower.trim();
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Whether NO_PROXY exempts this host, following the de-facto rules curl uses:
|
|
72
|
+
* a comma-separated list, `*` meaning everything, a leading dot ignored, and a
|
|
73
|
+
* bare entry matching that host plus its subdomains. An entry may pin a port,
|
|
74
|
+
* in which case the port must match too.
|
|
75
|
+
*/
|
|
76
|
+
export function isProxyBypassed(host, port, noProxy) {
|
|
77
|
+
if (!noProxy)
|
|
78
|
+
return false;
|
|
79
|
+
const entries = noProxy.split(',').map((e) => e.trim().toLowerCase()).filter(Boolean);
|
|
80
|
+
if (entries.includes('*'))
|
|
81
|
+
return true;
|
|
82
|
+
const target = host.toLowerCase();
|
|
83
|
+
return entries.some((entry) => {
|
|
84
|
+
let pattern = entry;
|
|
85
|
+
let entryPort;
|
|
86
|
+
// Only split a port off when it is unambiguous; a bare IPv6 literal is full
|
|
87
|
+
// of colons and must not be chopped at the first one.
|
|
88
|
+
const lastColon = pattern.lastIndexOf(':');
|
|
89
|
+
if (lastColon > 0 && !pattern.slice(lastColon + 1).includes(']') && /^\d+$/.test(pattern.slice(lastColon + 1))) {
|
|
90
|
+
entryPort = pattern.slice(lastColon + 1);
|
|
91
|
+
pattern = pattern.slice(0, lastColon);
|
|
92
|
+
}
|
|
93
|
+
if (entryPort !== undefined && entryPort !== port)
|
|
94
|
+
return false;
|
|
95
|
+
if (pattern.startsWith('.'))
|
|
96
|
+
pattern = pattern.slice(1);
|
|
97
|
+
return target === pattern || target.endsWith(`.${pattern}`);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The proxy URL that applies to `targetUrl`, or undefined for a direct connection.
|
|
102
|
+
*
|
|
103
|
+
* `wss:`/`ws:` are resolved as their HTTP equivalents because that is how the
|
|
104
|
+
* connection is actually made — a WebSocket starts life as an HTTP upgrade, so
|
|
105
|
+
* a user who set HTTPS_PROXY expects `wss://` to go through it.
|
|
106
|
+
*
|
|
107
|
+
* **An https target falls back to HTTP_PROXY, and that is a deliberate departure
|
|
108
|
+
* from curl.** curl reads only the variable matching the target's scheme, so
|
|
109
|
+
* HTTP_PROXY never applies to an https URL there. The `claude` CLI does not
|
|
110
|
+
* follow that rule: given HTTP_PROXY alone, whether exported in the environment
|
|
111
|
+
* or written into settings.json's `env` block, it tunnels api.anthropic.com
|
|
112
|
+
* through it. Both behaviours were measured against a local CONNECT proxy.
|
|
113
|
+
*
|
|
114
|
+
* Matching curl instead of `claude` is what broke Swttch/swttch#432 for the
|
|
115
|
+
* person who reported it: one settings.json, chat working because the CLI
|
|
116
|
+
* honored HTTP_PROXY, and the usage panel going out direct because this function
|
|
117
|
+
* did not. The user writes that file for `claude`, so `claude` is the behaviour
|
|
118
|
+
* to match — the plugin's whole premise is that anything possible from the CLI
|
|
119
|
+
* is possible from the GUI.
|
|
120
|
+
*/
|
|
121
|
+
export function resolveProxyUrl(targetUrl, env = process.env) {
|
|
122
|
+
const target = new URL(targetUrl);
|
|
123
|
+
const isSecure = target.protocol === 'https:' || target.protocol === 'wss:';
|
|
124
|
+
const port = target.port || (isSecure ? '443' : '80');
|
|
125
|
+
if (isProxyBypassed(target.hostname, port, readProxyEnv(env, 'NO_PROXY')))
|
|
126
|
+
return undefined;
|
|
127
|
+
const specific = isSecure
|
|
128
|
+
? readProxyEnv(env, 'HTTPS_PROXY') ?? readProxyEnv(env, 'HTTP_PROXY')
|
|
129
|
+
: readProxyEnv(env, 'HTTP_PROXY');
|
|
130
|
+
return specific ?? readProxyEnv(env, 'ALL_PROXY');
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* An agent that routes a connection to `targetUrl` through the configured proxy,
|
|
134
|
+
* or undefined when no proxy applies and the connection should go out directly.
|
|
135
|
+
*
|
|
136
|
+
* The result is an `http.Agent`, which is the one shape both callers accept:
|
|
137
|
+
* `https.request` takes it as `agent`, and `ws` takes the same object under the
|
|
138
|
+
* same name. That is why this package talks to the API over `node:https` rather
|
|
139
|
+
* than global `fetch` — `fetch` cannot be handed one of these, so using it would
|
|
140
|
+
* mean two separate proxy implementations for the two transports.
|
|
141
|
+
*
|
|
142
|
+
* A malformed proxy URL throws rather than falling back to a direct connection.
|
|
143
|
+
* On a machine that can only reach the internet through a proxy the direct
|
|
144
|
+
* attempt fails anyway, and it fails with a connection error that says nothing
|
|
145
|
+
* about the real cause; naming the bad setting is far more useful.
|
|
146
|
+
*/
|
|
147
|
+
export function proxyAgentFor(targetUrl, env = process.env, options = {}) {
|
|
148
|
+
const proxyUrl = resolveProxyUrl(targetUrl, env);
|
|
149
|
+
if (!proxyUrl)
|
|
150
|
+
return undefined;
|
|
151
|
+
let protocol;
|
|
152
|
+
try {
|
|
153
|
+
protocol = new URL(proxyUrl).protocol;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
throw new CcbError(`Proxy setting is not a valid URL: ${proxyUrl}`, 'invalid_proxy', 'Expected something like http://host:port or socks5://host:port');
|
|
157
|
+
}
|
|
158
|
+
// A deadline has to reach the AGENT, not just the request.
|
|
159
|
+
//
|
|
160
|
+
// While an agent is negotiating with the proxy there is no socket on the
|
|
161
|
+
// request yet, so aborting the request reaches nothing: measured against a
|
|
162
|
+
// proxy that accepted a CONNECT and stayed silent, an aborted request produced
|
|
163
|
+
// no 'socket', no 'error' and no 'close', and simply hung. The caller would get
|
|
164
|
+
// its timeout while the connection attempt carried on behind it.
|
|
165
|
+
//
|
|
166
|
+
// The two agents take different instruments for the same job, so each is given
|
|
167
|
+
// the one it honors rather than one being forced on both.
|
|
168
|
+
if (SOCKS_PROTOCOLS.has(protocol)) {
|
|
169
|
+
// socks-proxy-agent hands `timeout` to SocksClient, which applies it to the
|
|
170
|
+
// handshake. It does not read `signal`, and passing one is not merely
|
|
171
|
+
// ignored — its options type has no room for it.
|
|
172
|
+
return new SocksProxyAgent(proxyUrl, options.timeoutMs ? { timeout: options.timeoutMs } : undefined);
|
|
173
|
+
}
|
|
174
|
+
if (protocol === 'http:' || protocol === 'https:') {
|
|
175
|
+
// HttpsProxyAgent covers http: and https: alike, because every endpoint this
|
|
176
|
+
// package talks to is https/wss and therefore always reached by a CONNECT
|
|
177
|
+
// tunnel. Its options are spread into what it passes to net.connect, so a
|
|
178
|
+
// signal here destroys the socket the CONNECT wait is parked on and lets the
|
|
179
|
+
// failure surface. Measured: the error arrived 8ms after the abort.
|
|
180
|
+
return new HttpsProxyAgent(proxyUrl, options.signal ? { signal: options.signal } : undefined);
|
|
181
|
+
}
|
|
182
|
+
throw new CcbError(`Unsupported proxy scheme "${protocol.replace(':', '')}" in ${proxyUrl}`, 'invalid_proxy', 'Supported schemes: http, https, socks, socks4, socks4a, socks5, socks5h');
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
|
package/dist/stt/stream.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import WebSocket from 'ws';
|
|
2
2
|
import { getCredentials, getAccessToken } from '../battery/auth/index.js';
|
|
3
3
|
import { CcbError } from '../battery/errors.js';
|
|
4
|
+
import { proxyAgentFor } from '../proxy.js';
|
|
4
5
|
import { DEFAULT_KEYTERMS, packKeyterms } from './keyterms.js';
|
|
5
6
|
import { AUDIO_FORMAT, TranscriptMessageType, } from './types.js';
|
|
6
7
|
const VOICE_STREAM_URL = 'wss://api.anthropic.com/api/ws/speech_to_text/voice_stream';
|
|
@@ -43,13 +44,19 @@ export async function openSpeechToTextStream(handlers, options = {}, token) {
|
|
|
43
44
|
stt_provider: 'deepgram-nova3',
|
|
44
45
|
});
|
|
45
46
|
const keyterms = packKeyterms([...DEFAULT_KEYTERMS, ...(options.extraKeyterms ?? [])]);
|
|
46
|
-
const
|
|
47
|
+
const streamUrl = `${VOICE_STREAM_URL}?${query.toString()}`;
|
|
48
|
+
const socket = new WebSocket(streamUrl, {
|
|
47
49
|
headers: {
|
|
48
50
|
Authorization: `Bearer ${accessToken}`,
|
|
49
51
|
'x-app': 'swttch',
|
|
50
52
|
'anthropic-client-platform': 'claude_code_swttch',
|
|
51
53
|
...(keyterms ? { 'x-config-keyterms': keyterms } : {}),
|
|
52
54
|
},
|
|
55
|
+
// A WebSocket opens as an HTTP upgrade, so it needs the same proxy the usage
|
|
56
|
+
// requests use. Without this, dictation is dead on any machine that reaches
|
|
57
|
+
// the internet only through a proxy, even though the usage panel works.
|
|
58
|
+
// undefined here means "no proxy configured" and ws connects directly.
|
|
59
|
+
agent: proxyAgentFor(streamUrl),
|
|
53
60
|
});
|
|
54
61
|
let keepalive = null;
|
|
55
62
|
let closing = false;
|
package/dist/stt/stream.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../../src/stt/stream.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EACL,YAAY,EACZ,qBAAqB,GAKtB,MAAM,YAAY,CAAC;AAEpB,MAAM,gBAAgB,GAAG,4DAA4D,CAAC;AAEtF,sEAAsE;AACtE,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAEnC,4EAA4E;AAC5E,MAAM,cAAc,GAAG,IAAI,CAAC;AAE5B,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;AACxD,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC;AAE7D;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,QAA8B,EAC9B,UAA+B,EAAE,EACjC,KAAc;IAEd,MAAM,WAAW,GAAG,KAAK,IAAI,cAAc,CAAC,MAAM,cAAc,EAAE,CAAC,CAAC;IAEpE,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC;QAChC,QAAQ,EAAE,YAAY,CAAC,QAAQ;QAC/B,WAAW,EAAE,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC;QAC5C,QAAQ,EAAE,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC;QACvC,4EAA4E;QAC5E,cAAc,EAAE,KAAK;QACrB,gBAAgB,EAAE,MAAM;QACxB,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,IAAI;QAClC,uBAAuB,EAAE,MAAM;QAC/B,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,YAAY,EAAE,gBAAgB;KAC/B,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,GAAG,gBAAgB,EAAE,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAEvF,MAAM,
|
|
1
|
+
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../../src/stt/stream.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swttch/extend-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Companion tools for Swttch that live outside the plugin bundle: Claude Code account/usage SDK, the ccb CLI, and speech-to-text.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -60,6 +60,8 @@
|
|
|
60
60
|
"typescript": "^6.0.2"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
+
"https-proxy-agent": "^9.1.0",
|
|
64
|
+
"socks-proxy-agent": "^10.1.0",
|
|
63
65
|
"ws": "^8.21.3"
|
|
64
66
|
}
|
|
65
67
|
}
|