@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.
Files changed (87) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/_vendor/partial-json-parser/parser.d.mts.map +1 -1
  3. package/_vendor/partial-json-parser/parser.d.ts.map +1 -1
  4. package/_vendor/partial-json-parser/parser.js +14 -2
  5. package/_vendor/partial-json-parser/parser.js.map +1 -1
  6. package/_vendor/partial-json-parser/parser.mjs +14 -2
  7. package/_vendor/partial-json-parser/parser.mjs.map +1 -1
  8. package/client.d.mts +23 -1
  9. package/client.d.mts.map +1 -1
  10. package/client.d.ts +23 -1
  11. package/client.d.ts.map +1 -1
  12. package/client.js +54 -11
  13. package/client.js.map +1 -1
  14. package/client.mjs +54 -11
  15. package/client.mjs.map +1 -1
  16. package/core/api.d.mts +11 -0
  17. package/core/api.d.mts.map +1 -0
  18. package/core/api.d.ts +11 -0
  19. package/core/api.d.ts.map +1 -0
  20. package/core/api.js +3 -0
  21. package/core/api.js.map +1 -0
  22. package/core/api.mjs +2 -0
  23. package/core/api.mjs.map +1 -0
  24. package/core/error.d.mts +11 -0
  25. package/core/error.d.mts.map +1 -1
  26. package/core/error.d.ts +11 -0
  27. package/core/error.d.ts.map +1 -1
  28. package/core/error.js +17 -1
  29. package/core/error.js.map +1 -1
  30. package/core/error.mjs +15 -0
  31. package/core/error.mjs.map +1 -1
  32. package/core/middleware.d.mts +117 -0
  33. package/core/middleware.d.mts.map +1 -0
  34. package/core/middleware.d.ts +117 -0
  35. package/core/middleware.d.ts.map +1 -0
  36. package/core/middleware.js +167 -0
  37. package/core/middleware.js.map +1 -0
  38. package/core/middleware.mjs +161 -0
  39. package/core/middleware.mjs.map +1 -0
  40. package/index.d.mts +3 -2
  41. package/index.d.mts.map +1 -1
  42. package/index.d.ts +3 -2
  43. package/index.d.ts.map +1 -1
  44. package/index.js +2 -1
  45. package/index.js.map +1 -1
  46. package/index.mjs +2 -2
  47. package/index.mjs.map +1 -1
  48. package/internal/request-options.d.mts +7 -0
  49. package/internal/request-options.d.mts.map +1 -1
  50. package/internal/request-options.d.ts +7 -0
  51. package/internal/request-options.d.ts.map +1 -1
  52. package/internal/request-options.js.map +1 -1
  53. package/internal/request-options.mjs.map +1 -1
  54. package/lib/BetaMessageStream.d.mts.map +1 -1
  55. package/lib/BetaMessageStream.d.ts.map +1 -1
  56. package/lib/BetaMessageStream.js +4 -0
  57. package/lib/BetaMessageStream.js.map +1 -1
  58. package/lib/BetaMessageStream.mjs +4 -0
  59. package/lib/BetaMessageStream.mjs.map +1 -1
  60. package/package.json +1 -1
  61. package/resources/beta/messages/messages.d.mts.map +1 -1
  62. package/resources/beta/messages/messages.d.ts.map +1 -1
  63. package/resources/beta/messages/messages.js +2 -0
  64. package/resources/beta/messages/messages.js.map +1 -1
  65. package/resources/beta/messages/messages.mjs +2 -0
  66. package/resources/beta/messages/messages.mjs.map +1 -1
  67. package/resources/messages/messages.d.mts.map +1 -1
  68. package/resources/messages/messages.d.ts.map +1 -1
  69. package/resources/messages/messages.js +2 -0
  70. package/resources/messages/messages.js.map +1 -1
  71. package/resources/messages/messages.mjs +2 -0
  72. package/resources/messages/messages.mjs.map +1 -1
  73. package/src/_vendor/partial-json-parser/parser.ts +18 -2
  74. package/src/client.ts +77 -10
  75. package/src/core/api.ts +11 -0
  76. package/src/core/error.ts +15 -0
  77. package/src/core/middleware.ts +293 -0
  78. package/src/index.ts +10 -1
  79. package/src/internal/request-options.ts +8 -0
  80. package/src/lib/BetaMessageStream.ts +4 -0
  81. package/src/resources/beta/messages/messages.ts +2 -0
  82. package/src/resources/messages/messages.ts +2 -0
  83. package/src/version.ts +1 -1
  84. package/version.d.mts +1 -1
  85. package/version.d.ts +1 -1
  86. package/version.js +1 -1
  87. 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.fetch,
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
- try {
1157
- // use undefined this binding; fetch errors if bound to something else in browser/cloudflare
1158
- return await this.fetch.call(undefined, url, fetchOptions);
1159
- } finally {
1160
- clearTimeout(timeout);
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
 
@@ -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 { BaseAnthropic, Anthropic, type ClientOptions, HUMAN_PROMPT, AI_PROMPT } from './client';
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.100.0'; // x-release-please-version
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.100.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.100.0";
1
+ export declare const VERSION = "0.101.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
- exports.VERSION = '0.100.0'; // x-release-please-version
4
+ exports.VERSION = '0.101.0'; // x-release-please-version
5
5
  //# sourceMappingURL=version.js.map
package/version.mjs CHANGED
@@ -1,2 +1,2 @@
1
- export const VERSION = '0.100.0'; // x-release-please-version
1
+ export const VERSION = '0.101.0'; // x-release-please-version
2
2
  //# sourceMappingURL=version.mjs.map