@anthropic-ai/sdk 0.100.0 → 0.101.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/CHANGELOG.md +40 -0
- package/_vendor/partial-json-parser/parser.d.mts.map +1 -1
- package/_vendor/partial-json-parser/parser.d.ts.map +1 -1
- package/_vendor/partial-json-parser/parser.js +14 -2
- package/_vendor/partial-json-parser/parser.js.map +1 -1
- package/_vendor/partial-json-parser/parser.mjs +14 -2
- package/_vendor/partial-json-parser/parser.mjs.map +1 -1
- package/client.d.mts +23 -1
- package/client.d.mts.map +1 -1
- package/client.d.ts +23 -1
- package/client.d.ts.map +1 -1
- package/client.js +54 -11
- package/client.js.map +1 -1
- package/client.mjs +54 -11
- package/client.mjs.map +1 -1
- package/core/api.d.mts +11 -0
- package/core/api.d.mts.map +1 -0
- package/core/api.d.ts +11 -0
- package/core/api.d.ts.map +1 -0
- package/core/api.js +3 -0
- package/core/api.js.map +1 -0
- package/core/api.mjs +2 -0
- package/core/api.mjs.map +1 -0
- package/core/error.d.mts +11 -0
- package/core/error.d.mts.map +1 -1
- package/core/error.d.ts +11 -0
- package/core/error.d.ts.map +1 -1
- package/core/error.js +17 -1
- package/core/error.js.map +1 -1
- package/core/error.mjs +15 -0
- package/core/error.mjs.map +1 -1
- package/core/middleware.d.mts +117 -0
- package/core/middleware.d.mts.map +1 -0
- package/core/middleware.d.ts +117 -0
- package/core/middleware.d.ts.map +1 -0
- package/core/middleware.js +167 -0
- package/core/middleware.js.map +1 -0
- package/core/middleware.mjs +161 -0
- package/core/middleware.mjs.map +1 -0
- package/index.d.mts +3 -2
- package/index.d.mts.map +1 -1
- package/index.d.ts +3 -2
- package/index.d.ts.map +1 -1
- package/index.js +2 -1
- package/index.js.map +1 -1
- package/index.mjs +2 -2
- package/index.mjs.map +1 -1
- package/internal/request-options.d.mts +7 -0
- package/internal/request-options.d.mts.map +1 -1
- package/internal/request-options.d.ts +7 -0
- package/internal/request-options.d.ts.map +1 -1
- package/internal/request-options.js.map +1 -1
- package/internal/request-options.mjs.map +1 -1
- package/lib/BetaMessageStream.d.mts.map +1 -1
- package/lib/BetaMessageStream.d.ts.map +1 -1
- package/lib/BetaMessageStream.js +4 -0
- package/lib/BetaMessageStream.js.map +1 -1
- package/lib/BetaMessageStream.mjs +4 -0
- package/lib/BetaMessageStream.mjs.map +1 -1
- package/package.json +1 -1
- package/resources/beta/messages/messages.d.mts.map +1 -1
- package/resources/beta/messages/messages.d.ts.map +1 -1
- package/resources/beta/messages/messages.js +2 -0
- package/resources/beta/messages/messages.js.map +1 -1
- package/resources/beta/messages/messages.mjs +2 -0
- package/resources/beta/messages/messages.mjs.map +1 -1
- package/resources/messages/messages.d.mts.map +1 -1
- package/resources/messages/messages.d.ts.map +1 -1
- package/resources/messages/messages.js +2 -0
- package/resources/messages/messages.js.map +1 -1
- package/resources/messages/messages.mjs +2 -0
- package/resources/messages/messages.mjs.map +1 -1
- package/src/_vendor/partial-json-parser/parser.ts +18 -2
- package/src/client.ts +77 -10
- package/src/core/api.ts +11 -0
- package/src/core/error.ts +15 -0
- package/src/core/middleware.ts +293 -0
- package/src/index.ts +10 -1
- package/src/internal/request-options.ts +8 -0
- package/src/lib/BetaMessageStream.ts +4 -0
- package/src/resources/beta/messages/messages.ts +2 -0
- package/src/resources/messages/messages.ts +2 -0
- package/src/version.ts +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
package/src/client.ts
CHANGED
|
@@ -19,6 +19,14 @@ import { OAUTH_API_BETA_HEADER } from './lib/credentials/types';
|
|
|
19
19
|
import { TokenCache } from './lib/credentials/token-cache';
|
|
20
20
|
import { defaultCredentials, resolveCredentialsFromConfig } from './lib/credentials/credential-chain';
|
|
21
21
|
import type { AnthropicConfig } from './core/credentials';
|
|
22
|
+
import {
|
|
23
|
+
type Middleware,
|
|
24
|
+
isFetchOriginError,
|
|
25
|
+
isRetryableError,
|
|
26
|
+
wrapFetchWithMiddleware,
|
|
27
|
+
} from './core/middleware';
|
|
28
|
+
export type { Middleware, MiddlewareContext, MiddlewareNext } from './core/middleware';
|
|
29
|
+
export type { APIRequest } from './core/api';
|
|
22
30
|
import * as Pagination from './core/pagination';
|
|
23
31
|
import {
|
|
24
32
|
type PageCursorParams,
|
|
@@ -378,6 +386,14 @@ export interface ClientOptions {
|
|
|
378
386
|
*/
|
|
379
387
|
fetch?: Fetch | undefined;
|
|
380
388
|
|
|
389
|
+
/**
|
|
390
|
+
* {@link Middleware} functions that wrap every HTTP request made by the
|
|
391
|
+
* client.
|
|
392
|
+
*
|
|
393
|
+
* Middleware runs per HTTP attempt, including retries.
|
|
394
|
+
*/
|
|
395
|
+
middleware?: ReadonlyArray<Middleware> | undefined;
|
|
396
|
+
|
|
381
397
|
/**
|
|
382
398
|
* The maximum number of times that the client will retry a request in case of a
|
|
383
399
|
* temporary failure, like a network error or a 5XX error from the server.
|
|
@@ -457,6 +473,7 @@ export class BaseAnthropic {
|
|
|
457
473
|
logger: Logger;
|
|
458
474
|
logLevel: LogLevel | undefined;
|
|
459
475
|
fetchOptions: MergedRequestInit | undefined;
|
|
476
|
+
middleware: ReadonlyArray<Middleware>;
|
|
460
477
|
|
|
461
478
|
private fetch: Fetch;
|
|
462
479
|
#encoder: Opts.RequestEncoder;
|
|
@@ -532,6 +549,8 @@ export class BaseAnthropic {
|
|
|
532
549
|
this.fetch = options.fetch ?? Shims.getDefaultFetch();
|
|
533
550
|
this.#encoder = Opts.FallbackEncoder;
|
|
534
551
|
|
|
552
|
+
this.middleware = [...(options.middleware ?? [])];
|
|
553
|
+
|
|
535
554
|
const customHeadersEnv = readEnv('ANTHROPIC_CUSTOM_HEADERS');
|
|
536
555
|
if (customHeadersEnv) {
|
|
537
556
|
const parsed: Record<string, string> = {};
|
|
@@ -615,7 +634,7 @@ export class BaseAnthropic {
|
|
|
615
634
|
private _credentialResolverOptions() {
|
|
616
635
|
return {
|
|
617
636
|
baseURL: this.baseURL,
|
|
618
|
-
fetch: this.
|
|
637
|
+
fetch: this._credentialsFetch(),
|
|
619
638
|
userAgent: this.getUserAgent(),
|
|
620
639
|
onCacheWriteError: (err: unknown) => {
|
|
621
640
|
loggerFor(this).debug('credential cache write failed (best-effort)', err);
|
|
@@ -626,6 +645,19 @@ export class BaseAnthropic {
|
|
|
626
645
|
};
|
|
627
646
|
}
|
|
628
647
|
|
|
648
|
+
/**
|
|
649
|
+
* A `Fetch` for first-party credential token-exchange requests (OIDC
|
|
650
|
+
* federation jwt-bearer grants, user-OAuth refresh grants) that routes
|
|
651
|
+
* through this client's middleware chain, so middleware observes token
|
|
652
|
+
* traffic like any other request. Only client-level middleware applies:
|
|
653
|
+
* a minted token is shared across requests, so attributing the exchange
|
|
654
|
+
* to any one request's per-request middleware would be arbitrary. For the
|
|
655
|
+
* same reason, `ctx.options` is undefined for these requests.
|
|
656
|
+
*/
|
|
657
|
+
private _credentialsFetch(): Fetch {
|
|
658
|
+
return wrapFetchWithMiddleware(this.fetch, this.middleware);
|
|
659
|
+
}
|
|
660
|
+
|
|
629
661
|
private _makeTokenCache(provider: AccessTokenProvider): TokenCache {
|
|
630
662
|
return new TokenCache(provider, (err) => {
|
|
631
663
|
loggerFor(this).debug('advisory token refresh failed; serving cached token', err);
|
|
@@ -659,6 +691,7 @@ export class BaseAnthropic {
|
|
|
659
691
|
logLevel: this.logLevel,
|
|
660
692
|
fetch: this.fetch,
|
|
661
693
|
fetchOptions: this.fetchOptions,
|
|
694
|
+
middleware: this.middleware,
|
|
662
695
|
apiKey: this.apiKey,
|
|
663
696
|
authToken: this.authToken,
|
|
664
697
|
webhookKey: this.webhookKey,
|
|
@@ -974,7 +1007,7 @@ export class BaseAnthropic {
|
|
|
974
1007
|
}
|
|
975
1008
|
|
|
976
1009
|
const controller = new AbortController();
|
|
977
|
-
const response = await this.fetchWithTimeout(url, req, timeout, controller).catch(castToError);
|
|
1010
|
+
const response = await this.fetchWithTimeout(url, req, timeout, controller, options).catch(castToError);
|
|
978
1011
|
const headersTime = Date.now();
|
|
979
1012
|
|
|
980
1013
|
if (response instanceof globalThis.Error) {
|
|
@@ -989,6 +1022,25 @@ export class BaseAnthropic {
|
|
|
989
1022
|
const isTimeout =
|
|
990
1023
|
isAbortError(response) ||
|
|
991
1024
|
/timed? ?out/i.test(String(response) + ('cause' in response ? String(response.cause) : ''));
|
|
1025
|
+
|
|
1026
|
+
// Errors thrown by middleware propagate to the caller as-is — no retries, no
|
|
1027
|
+
// APIConnectionError wrapping — except retryable errors (timeouts/aborts,
|
|
1028
|
+
// APIConnectionErrors, and RetryableErrors, directly or in the `cause` chain),
|
|
1029
|
+
// which stay on the retry path.
|
|
1030
|
+
const hasMiddleware = this.middleware.length > 0 || !!options.middleware?.length;
|
|
1031
|
+
if (hasMiddleware && !isTimeout && !isRetryableError(response)) {
|
|
1032
|
+
loggerFor(this).info(`[${requestLogID}] middleware error (not retryable)`);
|
|
1033
|
+
loggerFor(this).debug(
|
|
1034
|
+
`[${requestLogID}] middleware error (not retryable)`,
|
|
1035
|
+
formatRequestDetails({
|
|
1036
|
+
retryOfRequestLogID,
|
|
1037
|
+
url,
|
|
1038
|
+
durationMs: headersTime - startTime,
|
|
1039
|
+
message: response.message,
|
|
1040
|
+
}),
|
|
1041
|
+
);
|
|
1042
|
+
throw response;
|
|
1043
|
+
}
|
|
992
1044
|
if (retriesRemaining) {
|
|
993
1045
|
loggerFor(this).info(
|
|
994
1046
|
`[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - ${retryMessage}`,
|
|
@@ -1019,6 +1071,11 @@ export class BaseAnthropic {
|
|
|
1019
1071
|
if (isTimeout) {
|
|
1020
1072
|
throw new Errors.APIConnectionTimeoutError();
|
|
1021
1073
|
}
|
|
1074
|
+
// a retryable middleware-origin error is still the caller's error: once retries are
|
|
1075
|
+
// exhausted it propagates as-is rather than wrapped in APIConnectionError
|
|
1076
|
+
if (hasMiddleware && !isFetchOriginError(response)) {
|
|
1077
|
+
throw response;
|
|
1078
|
+
}
|
|
1022
1079
|
throw new Errors.APIConnectionError({ cause: response });
|
|
1023
1080
|
}
|
|
1024
1081
|
|
|
@@ -1124,6 +1181,7 @@ export class BaseAnthropic {
|
|
|
1124
1181
|
init: RequestInit | undefined,
|
|
1125
1182
|
ms: number,
|
|
1126
1183
|
controller: AbortController,
|
|
1184
|
+
requestOptions?: FinalRequestOptions | undefined,
|
|
1127
1185
|
): Promise<Response> {
|
|
1128
1186
|
const { signal, method, ...options } = init || {};
|
|
1129
1187
|
// Avoid creating a closure over `this`, `init`, or `options` to prevent memory leaks.
|
|
@@ -1135,8 +1193,6 @@ export class BaseAnthropic {
|
|
|
1135
1193
|
const abort = this._makeAbort(controller);
|
|
1136
1194
|
if (signal) signal.addEventListener('abort', abort, { once: true });
|
|
1137
1195
|
|
|
1138
|
-
const timeout = setTimeout(abort, ms);
|
|
1139
|
-
|
|
1140
1196
|
const isReadableBody =
|
|
1141
1197
|
((globalThis as any).ReadableStream && options.body instanceof (globalThis as any).ReadableStream) ||
|
|
1142
1198
|
(typeof options.body === 'object' && options.body !== null && Symbol.asyncIterator in options.body);
|
|
@@ -1153,12 +1209,22 @@ export class BaseAnthropic {
|
|
|
1153
1209
|
fetchOptions.method = method.toUpperCase();
|
|
1154
1210
|
}
|
|
1155
1211
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1212
|
+
// Arm the timeout around the underlying fetch only, not the middleware
|
|
1213
|
+
// chain — middleware can take arbitrarily long (or call `next` more than
|
|
1214
|
+
// once), and each inner-fetch invocation gets its own `ms` timer.
|
|
1215
|
+
const baseFetch = this.fetch;
|
|
1216
|
+
const timedFetch: Fetch = async (innerUrl, innerInit) => {
|
|
1217
|
+
const timeout = setTimeout(abort, ms);
|
|
1218
|
+
try {
|
|
1219
|
+
return await baseFetch.call(undefined, innerUrl, innerInit);
|
|
1220
|
+
} finally {
|
|
1221
|
+
clearTimeout(timeout);
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
|
|
1225
|
+
const middleware = requestOptions?.middleware;
|
|
1226
|
+
const allMiddleware = middleware?.length ? [...this.middleware, ...middleware] : this.middleware;
|
|
1227
|
+
return await wrapFetchWithMiddleware(timedFetch, allMiddleware, requestOptions)(url, fetchOptions);
|
|
1162
1228
|
}
|
|
1163
1229
|
|
|
1164
1230
|
private async shouldRetry(response: Response, options: FinalRequestOptions): Promise<boolean> {
|
|
@@ -1438,6 +1504,7 @@ Anthropic.Beta = Beta;
|
|
|
1438
1504
|
|
|
1439
1505
|
export declare namespace Anthropic {
|
|
1440
1506
|
export type RequestOptions = Opts.RequestOptions;
|
|
1507
|
+
export type FinalRequestOptions = Opts.FinalRequestOptions;
|
|
1441
1508
|
|
|
1442
1509
|
export type { ApiKeySetter };
|
|
1443
1510
|
|
package/src/core/api.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FinalizedRequestInit } from '../internal/types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* An HTTP request as it will be passed to `fetch`, the `RequestInit` plus the request `url`.
|
|
5
|
+
*
|
|
6
|
+
* `headers` is always a `Headers` instance and may be mutated in place.
|
|
7
|
+
*/
|
|
8
|
+
export type APIRequest = FinalizedRequestInit & {
|
|
9
|
+
/** The fully-built request URL, including query parameters. */
|
|
10
|
+
url: string;
|
|
11
|
+
};
|
package/src/core/error.ts
CHANGED
|
@@ -128,6 +128,21 @@ export class APIConnectionTimeoutError extends APIConnectionError {
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
/**
|
|
132
|
+
* An error that opts into the SDK's retry policy: throw it (e.g. from
|
|
133
|
+
* middleware) to have the attempt retried.
|
|
134
|
+
*
|
|
135
|
+
* Note that the request will only be retried when `maxRetries` has not been exhausted.
|
|
136
|
+
*/
|
|
137
|
+
export class RetryableError extends AnthropicError {
|
|
138
|
+
constructor(message?: string, { cause }: { cause?: unknown } = {}) {
|
|
139
|
+
super(message ?? 'Retryable error.');
|
|
140
|
+
// in some environments the 'cause' property is already declared
|
|
141
|
+
// @ts-ignore
|
|
142
|
+
if (cause !== undefined) this.cause = cause;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
131
146
|
export class BadRequestError extends APIError<400, Headers> {}
|
|
132
147
|
|
|
133
148
|
export class AuthenticationError extends APIError<401, Headers> {}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import type { Fetch } from '../internal/builtin-types';
|
|
2
|
+
import { castToError, isAbortError } from '../internal/errors';
|
|
3
|
+
import { addRequestID } from '../internal/parse';
|
|
4
|
+
import type { FinalRequestOptions } from '../internal/request-options';
|
|
5
|
+
import type { APIRequest } from './api';
|
|
6
|
+
import { AnthropicError, APIConnectionError, RetryableError } from './error';
|
|
7
|
+
import { Stream } from './streaming';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Invokes the rest of the middleware chain, ending with the underlying `fetch`.
|
|
11
|
+
*
|
|
12
|
+
* This function can be invoked multiple times.
|
|
13
|
+
*/
|
|
14
|
+
export type MiddlewareNext = (request: APIRequest) => Promise<Response>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Helpers passed to each middleware alongside `next`, scoped to the request
|
|
18
|
+
* in flight (one context is shared by every middleware in the chain).
|
|
19
|
+
*/
|
|
20
|
+
export interface MiddlewareContext {
|
|
21
|
+
/**
|
|
22
|
+
* The SDK request options the API call in flight was made with: `method`,
|
|
23
|
+
* `path`, the pre-encoded `body`, `stream`, etc.
|
|
24
|
+
*
|
|
25
|
+
* `undefined` when the chain isn't running for an SDK API request, i.e.
|
|
26
|
+
* for credential token-exchange requests.
|
|
27
|
+
*/
|
|
28
|
+
readonly options?: FinalRequestOptions | undefined;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Parse a response body the way the SDK would for the request in flight:
|
|
32
|
+
*
|
|
33
|
+
* - JSON responses are decoded, with the non-enumerable `_request_id`
|
|
34
|
+
* property attached like SDK return values, and anything else resolves
|
|
35
|
+
* to the body text.
|
|
36
|
+
* - For streaming requests ({@link options}`.stream`), resolves immediately
|
|
37
|
+
* with a {@link Stream} reading an independent copy of the response body —
|
|
38
|
+
* iterating it doesn't consume the client's events, and aborting or
|
|
39
|
+
* `break`ing out of it doesn't cancel the underlying request. Each call
|
|
40
|
+
* returns a fresh `Stream` (streams are single-consumer, so they aren't
|
|
41
|
+
* cached). Error (non-2xx) responses parse as JSON/text rather than as a
|
|
42
|
+
* stream, mirroring the SDK's own handling.
|
|
43
|
+
* - For binary requests, resolves with the `Response` itself, unconsumed.
|
|
44
|
+
*
|
|
45
|
+
* Reads through an internal `response.clone()`, so the response stays
|
|
46
|
+
* readable: the client (and any other middleware) can still consume the
|
|
47
|
+
* body afterwards. Non-stream results are cached per `Response` and shared
|
|
48
|
+
* across the middleware chain, so repeated calls cost a single read.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* const mw: Middleware = async (request, next, ctx) => {
|
|
53
|
+
* const response = await next(request);
|
|
54
|
+
* const data = await ctx.parse<Message>(response);
|
|
55
|
+
* if (data.type === 'message') console.log(data.usage);
|
|
56
|
+
* return response;
|
|
57
|
+
* };
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
parse<T = unknown>(response: Response): Promise<T>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A function that wraps each HTTP request made by the client.
|
|
65
|
+
*
|
|
66
|
+
* Middleware may observe or modify the request before calling `next`, observe
|
|
67
|
+
* or replace the response, short-circuit by returning a `Response` without
|
|
68
|
+
* calling `next`, or call `next` multiple times to implement custom retries.
|
|
69
|
+
*
|
|
70
|
+
* Middleware must not consume the body of the `Response` it returns - the
|
|
71
|
+
* client still needs to read it. To inspect the body, use
|
|
72
|
+
* `await ctx.parse(response)` (cached, leaves the body readable) or read a
|
|
73
|
+
* clone (`await response.clone().text()`); to transform it, return a
|
|
74
|
+
* replacement, e.g. `new Response(body, response)`.
|
|
75
|
+
*
|
|
76
|
+
* Middleware runs per HTTP attempt, inside the SDK's retry loop; the attempt
|
|
77
|
+
* number is available via the `X-Stainless-Retry-Count` request header. An
|
|
78
|
+
* error thrown from middleware propagates to the caller as-is.
|
|
79
|
+
*
|
|
80
|
+
* Middleware errors are **not** retried apart from connection-level errors:
|
|
81
|
+
* timeout/abort errors, errors thrown by `fetch()`, and `APIConnectionError`s
|
|
82
|
+
* or `RetryableError`s — thrown directly or present anywhere in an error's
|
|
83
|
+
* `cause` chain. Retryable middleware errors still propagate to the caller
|
|
84
|
+
* as-is once retries are exhausted.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* const logger: Middleware = async (request, next) => {
|
|
89
|
+
* console.log('->', request.method, request.url);
|
|
90
|
+
* const response = await next(request);
|
|
91
|
+
* console.log('<-', response.status, request.url);
|
|
92
|
+
* return response;
|
|
93
|
+
* };
|
|
94
|
+
*
|
|
95
|
+
* const client = new Anthropic({ middleware: [logger] });
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export type Middleware = (
|
|
99
|
+
request: APIRequest,
|
|
100
|
+
next: MiddlewareNext,
|
|
101
|
+
ctx: MiddlewareContext,
|
|
102
|
+
) => Promise<Response>;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Errors thrown by the underlying `fetch`, as opposed to by a middleware.
|
|
106
|
+
*
|
|
107
|
+
* Tracked so the client can apply its connection-error retry policy to
|
|
108
|
+
* transport failures while letting errors thrown by middleware propagate to
|
|
109
|
+
* the caller untouched.
|
|
110
|
+
*/
|
|
111
|
+
const fetchOriginErrors = new WeakSet<object>();
|
|
112
|
+
|
|
113
|
+
/** Whether `err` was thrown by the underlying `fetch` rather than by a middleware. */
|
|
114
|
+
export function isFetchOriginError(err: unknown): boolean {
|
|
115
|
+
return typeof err === 'object' && err !== null && fetchOriginErrors.has(err);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Whether an error thrown by middleware should stay on the SDK's
|
|
120
|
+
* connection-error retry policy: fetch-origin, abort, `APIConnectionError`, or
|
|
121
|
+
* `RetryableError` — checked through the error's `cause` chain.
|
|
122
|
+
*/
|
|
123
|
+
export function isRetryableError(err: unknown): boolean {
|
|
124
|
+
const seen = new Set<unknown>(); // guard against `cause` cycles
|
|
125
|
+
while (typeof err === 'object' && err !== null && !seen.has(err)) {
|
|
126
|
+
seen.add(err);
|
|
127
|
+
if (
|
|
128
|
+
isFetchOriginError(err) ||
|
|
129
|
+
isAbortError(err) ||
|
|
130
|
+
err instanceof APIConnectionError ||
|
|
131
|
+
err instanceof RetryableError
|
|
132
|
+
) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
err = (err as { cause?: unknown }).cause;
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Wraps `fetchFn` so each call runs through `middleware`, keeping the same
|
|
142
|
+
* call signature as `fetch` itself.
|
|
143
|
+
*
|
|
144
|
+
* With no middleware, calls are passed straight through to `fetchFn`.
|
|
145
|
+
* Otherwise the arguments are normalized into an {@link APIRequest} (headers
|
|
146
|
+
* coerced to a `Headers` instance, URL stringified) before entering the
|
|
147
|
+
* chain. The chain is composed per call, so mutations of a `middleware`
|
|
148
|
+
* array are picked up by later requests.
|
|
149
|
+
*
|
|
150
|
+
* `options` — the SDK request options behind this call, when there are any —
|
|
151
|
+
* is surfaced to middleware as `ctx.options` and drives `ctx.parse`.
|
|
152
|
+
*/
|
|
153
|
+
export function wrapFetchWithMiddleware(
|
|
154
|
+
fetchFn: Fetch,
|
|
155
|
+
middleware: readonly Middleware[],
|
|
156
|
+
options?: FinalRequestOptions | undefined,
|
|
157
|
+
): Fetch {
|
|
158
|
+
return async (url, init = {}) => {
|
|
159
|
+
if (middleware.length === 0) {
|
|
160
|
+
// use undefined this binding; fetch errors if bound to something else in browser/cloudflare
|
|
161
|
+
return fetchFn.call(undefined, url, init);
|
|
162
|
+
}
|
|
163
|
+
const headers = init.headers instanceof Headers ? init.headers : new Headers(init.headers);
|
|
164
|
+
const response = await applyMiddleware(
|
|
165
|
+
fetchFn,
|
|
166
|
+
middleware,
|
|
167
|
+
options,
|
|
168
|
+
)({
|
|
169
|
+
...init,
|
|
170
|
+
headers,
|
|
171
|
+
url:
|
|
172
|
+
typeof url === 'string' ? url
|
|
173
|
+
: url instanceof URL ? url.href
|
|
174
|
+
: url.url,
|
|
175
|
+
});
|
|
176
|
+
// Catch a footgun before the client tries to read the body itself and
|
|
177
|
+
// fails with a confusing low-level stream error.
|
|
178
|
+
if (response.bodyUsed || response.body?.locked) {
|
|
179
|
+
throw new AnthropicError(
|
|
180
|
+
'middleware consumed the response body; use response.clone() to inspect it, ' +
|
|
181
|
+
'or return new Response(body, response) to consume and replace it',
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return response;
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Creates the {@link MiddlewareContext} shared by every middleware in one chain.
|
|
190
|
+
*/
|
|
191
|
+
function createMiddlewareContext(options: FinalRequestOptions | undefined): MiddlewareContext {
|
|
192
|
+
// Keyed on the Response so each `next()` call's response (e.g. with custom
|
|
193
|
+
// retries, or a middleware swapping in a replacement) parses independently,
|
|
194
|
+
// while several middleware parsing the same response share a single read.
|
|
195
|
+
const cache = new WeakMap<Response, Promise<unknown>>();
|
|
196
|
+
return {
|
|
197
|
+
options,
|
|
198
|
+
parse<T>(response: Response): Promise<T> {
|
|
199
|
+
// Streams are single-consumer, so caching one would hand later callers
|
|
200
|
+
// an already-consumed stream; every call gets a fresh clone-backed one.
|
|
201
|
+
if (options?.stream && response.ok) {
|
|
202
|
+
return parseMiddlewareResponse(response, options) as Promise<T>;
|
|
203
|
+
}
|
|
204
|
+
let parsed = cache.get(response);
|
|
205
|
+
if (!parsed) {
|
|
206
|
+
parsed = parseMiddlewareResponse(response, options);
|
|
207
|
+
cache.set(response, parsed);
|
|
208
|
+
}
|
|
209
|
+
return parsed as Promise<T>;
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Mirrors the client's own response parsing (`defaultParseResponse` in
|
|
216
|
+
* `internal/parse.ts`), reading through a clone so the body stays available
|
|
217
|
+
* to the rest of the chain and the client itself.
|
|
218
|
+
*/
|
|
219
|
+
async function parseMiddlewareResponse(
|
|
220
|
+
response: Response,
|
|
221
|
+
options: FinalRequestOptions | undefined,
|
|
222
|
+
): Promise<unknown> {
|
|
223
|
+
if (response.bodyUsed || response.body?.locked) {
|
|
224
|
+
throw new AnthropicError(
|
|
225
|
+
'cannot ctx.parse() a response whose body was already consumed; ' +
|
|
226
|
+
'call ctx.parse() instead of reading the body, or read via response.clone()',
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Error responses parse as JSON/text below — the SDK only stream-parses
|
|
231
|
+
// successful responses, and middleware typically wants the error body.
|
|
232
|
+
if (options?.stream && response.ok) {
|
|
233
|
+
// A fresh controller rather than the request's own: aborting (or
|
|
234
|
+
// `break`ing out of) the middleware's stream must not cancel the
|
|
235
|
+
// in-flight request the client is still reading.
|
|
236
|
+
const streamClass = options.__streamClass ?? Stream;
|
|
237
|
+
return streamClass.fromSSEResponse(response.clone(), new AbortController());
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// fetch refuses to read the body when the status code is 204.
|
|
241
|
+
if (response.status === 204) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (options?.__binaryResponse) {
|
|
246
|
+
return response;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const contentType = response.headers.get('content-type');
|
|
250
|
+
const mediaType = contentType?.split(';')[0]?.trim();
|
|
251
|
+
const isJSON = mediaType?.includes('application/json') || mediaType?.endsWith('+json');
|
|
252
|
+
if (isJSON) {
|
|
253
|
+
if (response.headers.get('content-length') === '0') {
|
|
254
|
+
// if there is no content we can't do anything
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
return addRequestID(await response.clone().json(), response);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return await response.clone().text();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Composes `middleware` around `fetchFn` and returns the entry point of the chain.
|
|
265
|
+
*/
|
|
266
|
+
export function applyMiddleware(
|
|
267
|
+
fetchFn: Fetch,
|
|
268
|
+
middleware: readonly Middleware[],
|
|
269
|
+
options?: FinalRequestOptions | undefined,
|
|
270
|
+
): MiddlewareNext {
|
|
271
|
+
// use undefined this binding; fetch errors if bound to something else in browser/cloudflare
|
|
272
|
+
let next: MiddlewareNext = async ({ url, ...init }) => {
|
|
273
|
+
try {
|
|
274
|
+
return await fetchFn.call(undefined, url, init);
|
|
275
|
+
} catch (err) {
|
|
276
|
+
// Brand the error as fetch-origin, normalizing with `castToError` first since a
|
|
277
|
+
// WeakSet can't hold primitives and the brand must be on the same object the
|
|
278
|
+
// client's own `castToError` will later pass through.
|
|
279
|
+
const error = castToError(err);
|
|
280
|
+
fetchOriginErrors.add(error);
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const ctx = createMiddlewareContext(options);
|
|
286
|
+
for (let i = middleware.length - 1; i >= 0; i--) {
|
|
287
|
+
const mw = middleware[i]!;
|
|
288
|
+
const nextInner = next;
|
|
289
|
+
next = async (request) => mw(request, nextInner, ctx);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return next;
|
|
293
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,15 @@ export { Anthropic as default } from './client';
|
|
|
4
4
|
|
|
5
5
|
export { type Uploadable, toFile } from './core/uploads';
|
|
6
6
|
export { APIPromise } from './core/api-promise';
|
|
7
|
-
export {
|
|
7
|
+
export { type Middleware, type MiddlewareContext, type MiddlewareNext } from './core/middleware';
|
|
8
|
+
export {
|
|
9
|
+
BaseAnthropic,
|
|
10
|
+
Anthropic,
|
|
11
|
+
type APIRequest,
|
|
12
|
+
type ClientOptions,
|
|
13
|
+
HUMAN_PROMPT,
|
|
14
|
+
AI_PROMPT,
|
|
15
|
+
} from './client';
|
|
8
16
|
export { PagePromise } from './core/pagination';
|
|
9
17
|
export {
|
|
10
18
|
AnthropicError,
|
|
@@ -12,6 +20,7 @@ export {
|
|
|
12
20
|
APIConnectionError,
|
|
13
21
|
APIConnectionTimeoutError,
|
|
14
22
|
APIUserAbortError,
|
|
23
|
+
RetryableError,
|
|
15
24
|
NotFoundError,
|
|
16
25
|
ConflictError,
|
|
17
26
|
RateLimitError,
|
|
@@ -4,6 +4,7 @@ import { NullableHeaders } from './headers';
|
|
|
4
4
|
|
|
5
5
|
import type { BodyInit } from './builtin-types';
|
|
6
6
|
import { Stream } from '../core/streaming';
|
|
7
|
+
import type { Middleware } from '../core/middleware';
|
|
7
8
|
import type { HTTPMethod, MergedRequestInit } from './types';
|
|
8
9
|
import { type HeadersLike } from './headers';
|
|
9
10
|
|
|
@@ -66,6 +67,13 @@ export type RequestOptions = {
|
|
|
66
67
|
*/
|
|
67
68
|
signal?: AbortSignal | undefined | null;
|
|
68
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Additional {@link Middleware} to wrap this request's HTTP attempts.
|
|
72
|
+
*
|
|
73
|
+
* These run after any client-level middleware and apply to every attempt of this request, including retries.
|
|
74
|
+
*/
|
|
75
|
+
middleware?: ReadonlyArray<Middleware> | undefined;
|
|
76
|
+
|
|
69
77
|
/**
|
|
70
78
|
* A unique key for this request to enable idempotency.
|
|
71
79
|
*/
|
|
@@ -578,6 +578,9 @@ export class BetaMessageStream<ParsedT = null> implements AsyncIterable<BetaMess
|
|
|
578
578
|
snapshot.container = event.delta.container;
|
|
579
579
|
snapshot.stop_reason = event.delta.stop_reason;
|
|
580
580
|
snapshot.stop_sequence = event.delta.stop_sequence;
|
|
581
|
+
if (event.delta.stop_details != null) {
|
|
582
|
+
snapshot.stop_details = event.delta.stop_details;
|
|
583
|
+
}
|
|
581
584
|
snapshot.usage.output_tokens = event.usage.output_tokens;
|
|
582
585
|
snapshot.context_management = event.context_management;
|
|
583
586
|
|
|
@@ -679,6 +682,7 @@ export class BetaMessageStream<ParsedT = null> implements AsyncIterable<BetaMess
|
|
|
679
682
|
snapshot.content[event.index] = {
|
|
680
683
|
...snapshotContent,
|
|
681
684
|
content: (snapshotContent.content || '') + event.delta.content,
|
|
685
|
+
encrypted_content: event.delta.encrypted_content,
|
|
682
686
|
};
|
|
683
687
|
}
|
|
684
688
|
break;
|
|
@@ -60,6 +60,8 @@ const DEPRECATED_MODELS: {
|
|
|
60
60
|
'claude-2.0': 'July 21st, 2025',
|
|
61
61
|
'claude-3-7-sonnet-latest': 'February 19th, 2026',
|
|
62
62
|
'claude-3-7-sonnet-20250219': 'February 19th, 2026',
|
|
63
|
+
'claude-opus-4-1': 'August 5th, 2026',
|
|
64
|
+
'claude-opus-4-1-20250805': 'August 5th, 2026',
|
|
63
65
|
};
|
|
64
66
|
|
|
65
67
|
const MODELS_TO_WARN_WITH_THINKING_ENABLED: Model[] = ['claude-mythos-preview', 'claude-opus-4-6'];
|
|
@@ -1263,6 +1263,8 @@ const DEPRECATED_MODELS: {
|
|
|
1263
1263
|
'claude-opus-4-20250514': 'June 15th, 2026',
|
|
1264
1264
|
'claude-sonnet-4-0': 'June 15th, 2026',
|
|
1265
1265
|
'claude-sonnet-4-20250514': 'June 15th, 2026',
|
|
1266
|
+
'claude-opus-4-1': 'August 5th, 2026',
|
|
1267
|
+
'claude-opus-4-1-20250805': 'August 5th, 2026',
|
|
1266
1268
|
};
|
|
1267
1269
|
|
|
1268
1270
|
const MODELS_TO_WARN_WITH_THINKING_ENABLED: Model[] = ['claude-mythos-preview', 'claude-opus-4-6'];
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.101.0'; // x-release-please-version
|
package/version.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.101.0";
|
|
2
2
|
//# sourceMappingURL=version.d.mts.map
|
package/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.101.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/version.js
CHANGED
package/version.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.101.0'; // x-release-please-version
|
|
2
2
|
//# sourceMappingURL=version.mjs.map
|