feat: Complete zCode CLI X with Telegram bot integration

- Add full Telegram bot functionality with Z.AI API integration
- Implement 4 tools: Bash, FileEdit, WebSearch, Git
- Add 3 agents: Code Reviewer, Architect, DevOps Engineer
- Add 6 skills for common coding tasks
- Add systemd service file for 24/7 operation
- Add nginx configuration for HTTPS webhook
- Add comprehensive documentation
- Implement WebSocket server for real-time updates
- Add logging system with Winston
- Add environment validation

🤖 zCode CLI X - Agentic coder with Z.AI + Telegram integration
This commit is contained in:
admin
2026-05-05 09:01:26 +00:00
Unverified
parent 4a7035dd92
commit 875c7f9b91
24688 changed files with 3224957 additions and 221 deletions

View File

@@ -0,0 +1,3 @@
# Partial JSON Parser
Vendored from https://www.npmjs.com/package/partial-json-parser and updated to use TypeScript.

View File

@@ -0,0 +1,264 @@
type Token = {
type: string;
value: string;
};
const tokenize = (input: string): Token[] => {
let current = 0;
let tokens: Token[] = [];
while (current < input.length) {
let char = input[current];
if (char === '\\') {
current++;
continue;
}
if (char === '{') {
tokens.push({
type: 'brace',
value: '{',
});
current++;
continue;
}
if (char === '}') {
tokens.push({
type: 'brace',
value: '}',
});
current++;
continue;
}
if (char === '[') {
tokens.push({
type: 'paren',
value: '[',
});
current++;
continue;
}
if (char === ']') {
tokens.push({
type: 'paren',
value: ']',
});
current++;
continue;
}
if (char === ':') {
tokens.push({
type: 'separator',
value: ':',
});
current++;
continue;
}
if (char === ',') {
tokens.push({
type: 'delimiter',
value: ',',
});
current++;
continue;
}
if (char === '"') {
let value = '';
let danglingQuote = false;
char = input[++current];
while (char !== '"') {
if (current === input.length) {
danglingQuote = true;
break;
}
if (char === '\\') {
current++;
if (current === input.length) {
danglingQuote = true;
break;
}
value += char + input[current];
char = input[++current];
} else {
value += char;
char = input[++current];
}
}
char = input[++current];
if (!danglingQuote) {
tokens.push({
type: 'string',
value,
});
}
continue;
}
let WHITESPACE = /\s/;
if (char && WHITESPACE.test(char)) {
current++;
continue;
}
let NUMBERS = /[0-9]/;
if ((char && NUMBERS.test(char)) || char === '-' || char === '.') {
let value = '';
if (char === '-') {
value += char;
char = input[++current];
}
while ((char && NUMBERS.test(char)) || char === '.') {
value += char;
char = input[++current];
}
tokens.push({
type: 'number',
value,
});
continue;
}
let LETTERS = /[a-z]/i;
if (char && LETTERS.test(char)) {
let value = '';
while (char && LETTERS.test(char)) {
if (current === input.length) {
break;
}
value += char;
char = input[++current];
}
if (value == 'true' || value == 'false' || value === 'null') {
tokens.push({
type: 'name',
value,
});
} else {
// unknown token, e.g. `nul` which isn't quite `null`
current++;
continue;
}
continue;
}
current++;
}
return tokens;
},
strip = (tokens: Token[]): Token[] => {
if (tokens.length === 0) {
return tokens;
}
let lastToken = tokens[tokens.length - 1]!;
switch (lastToken.type) {
case 'separator':
tokens = tokens.slice(0, tokens.length - 1);
return strip(tokens);
break;
case 'number':
let lastCharacterOfLastToken = lastToken.value[lastToken.value.length - 1];
if (lastCharacterOfLastToken === '.' || lastCharacterOfLastToken === '-') {
tokens = tokens.slice(0, tokens.length - 1);
return strip(tokens);
}
case 'string':
let tokenBeforeTheLastToken = tokens[tokens.length - 2];
if (tokenBeforeTheLastToken?.type === 'delimiter') {
tokens = tokens.slice(0, tokens.length - 1);
return strip(tokens);
} else if (tokenBeforeTheLastToken?.type === 'brace' && tokenBeforeTheLastToken.value === '{') {
tokens = tokens.slice(0, tokens.length - 1);
return strip(tokens);
}
break;
case 'delimiter':
tokens = tokens.slice(0, tokens.length - 1);
return strip(tokens);
break;
}
return tokens;
},
unstrip = (tokens: Token[]): Token[] => {
let tail: string[] = [];
tokens.map((token) => {
if (token.type === 'brace') {
if (token.value === '{') {
tail.push('}');
} else {
tail.splice(tail.lastIndexOf('}'), 1);
}
}
if (token.type === 'paren') {
if (token.value === '[') {
tail.push(']');
} else {
tail.splice(tail.lastIndexOf(']'), 1);
}
}
});
if (tail.length > 0) {
tail.reverse().map((item) => {
if (item === '}') {
tokens.push({
type: 'brace',
value: '}',
});
} else if (item === ']') {
tokens.push({
type: 'paren',
value: ']',
});
}
});
}
return tokens;
},
generate = (tokens: Token[]): string => {
let output = '';
tokens.map((token) => {
switch (token.type) {
case 'string':
output += '"' + token.value + '"';
break;
default:
output += token.value;
break;
}
});
return output;
},
partialParse = (input: string): unknown => JSON.parse(generate(unstrip(strip(tokenize(input)))));
export { partialParse };

View File

@@ -0,0 +1,2 @@
/** @deprecated Import from ./core/api-promise instead */
export * from './core/api-promise';

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
# `core`
This directory holds public modules implementing non-resource-specific SDK functionality.

View File

@@ -0,0 +1,101 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { type BaseAnthropic } from '../client';
import { type PromiseOrValue } from '../internal/types';
import {
type APIResponseProps,
type WithRequestID,
defaultParseResponse,
addRequestID,
} from '../internal/parse';
/**
* A subclass of `Promise` providing additional helper methods
* for interacting with the SDK.
*/
export class APIPromise<T> extends Promise<WithRequestID<T>> {
private parsedPromise: Promise<WithRequestID<T>> | undefined;
#client: BaseAnthropic;
constructor(
client: BaseAnthropic,
private responsePromise: Promise<APIResponseProps>,
private parseResponse: (
client: BaseAnthropic,
props: APIResponseProps,
) => PromiseOrValue<WithRequestID<T>> = defaultParseResponse,
) {
super((resolve) => {
// this is maybe a bit weird but this has to be a no-op to not implicitly
// parse the response body; instead .then, .catch, .finally are overridden
// to parse the response
resolve(null as any);
});
this.#client = client;
}
_thenUnwrap<U>(transform: (data: T, props: APIResponseProps) => U): APIPromise<U> {
return new APIPromise(this.#client, this.responsePromise, async (client, props) =>
addRequestID(transform(await this.parseResponse(client, props), props), props.response),
);
}
/**
* Gets the raw `Response` instance instead of parsing the response
* data.
*
* If you want to parse the response body but still get the `Response`
* instance, you can use {@link withResponse()}.
*
* 👋 Getting the wrong TypeScript type for `Response`?
* Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]`
* to your `tsconfig.json`.
*/
asResponse(): Promise<Response> {
return this.responsePromise.then((p) => p.response);
}
/**
* Gets the parsed response data, the raw `Response` instance and the ID of the request,
* returned via the `request-id` header which is useful for debugging requests and resporting
* issues to Anthropic.
*
* If you just want to get the raw `Response` instance without parsing it,
* you can use {@link asResponse()}.
*
* 👋 Getting the wrong TypeScript type for `Response`?
* Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]`
* to your `tsconfig.json`.
*/
async withResponse(): Promise<{ data: T; response: Response; request_id: string | null | undefined }> {
const [data, response] = await Promise.all([this.parse(), this.asResponse()]);
return { data, response, request_id: response.headers.get('request-id') };
}
private parse(): Promise<WithRequestID<T>> {
if (!this.parsedPromise) {
this.parsedPromise = this.responsePromise.then(
(data) => this.parseResponse(this.#client, data) as any as Promise<WithRequestID<T>>,
);
}
return this.parsedPromise;
}
override then<TResult1 = WithRequestID<T>, TResult2 = never>(
onfulfilled?: ((value: WithRequestID<T>) => TResult1 | PromiseLike<TResult1>) | undefined | null,
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null,
): Promise<TResult1 | TResult2> {
return this.parse().then(onfulfilled, onrejected);
}
override catch<TResult = never>(
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null,
): Promise<WithRequestID<T> | TResult> {
return this.parse().catch(onrejected);
}
override finally(onfinally?: (() => void) | undefined | null): Promise<WithRequestID<T>> {
return this.parse().finally(onfinally);
}
}

View File

@@ -0,0 +1,145 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { castToError } from '../internal/errors';
import type { ErrorType } from '../resources/shared';
export class AnthropicError extends Error {}
export class APIError<
TStatus extends number | undefined = number | undefined,
THeaders extends Headers | undefined = Headers | undefined,
TError extends Object | undefined = Object | undefined,
> extends AnthropicError {
/** HTTP status for the response that caused the error */
readonly status: TStatus;
/** HTTP headers for the response that caused the error */
readonly headers: THeaders;
/** JSON body of the response that caused the error */
readonly error: TError;
readonly requestID: string | null | undefined;
/** The `error.type` from the API response body, e.g. `"rate_limit_error"` */
readonly type: ErrorType | null;
constructor(
status: TStatus,
error: TError,
message: string | undefined,
headers: THeaders,
type?: ErrorType | null,
) {
super(`${APIError.makeMessage(status, error, message)}`);
this.status = status;
this.headers = headers;
this.requestID = headers?.get('request-id');
this.error = error;
this.type = type ?? null;
}
private static makeMessage(status: number | undefined, error: any, message: string | undefined) {
const msg =
error?.message ?
typeof error.message === 'string' ?
error.message
: JSON.stringify(error.message)
: error ? JSON.stringify(error)
: message;
if (status && msg) {
return `${status} ${msg}`;
}
if (status) {
return `${status} status code (no body)`;
}
if (msg) {
return msg;
}
return '(no status code or body)';
}
static generate(
status: number | undefined,
errorResponse: Object | undefined,
message: string | undefined,
headers: Headers | undefined,
): APIError {
if (!status || !headers) {
return new APIConnectionError({ message, cause: castToError(errorResponse) });
}
const error = errorResponse as Record<string, any>;
const type = error?.['error']?.['type'] as ErrorType | undefined;
if (status === 400) {
return new BadRequestError(status, error, message, headers, type);
}
if (status === 401) {
return new AuthenticationError(status, error, message, headers, type);
}
if (status === 403) {
return new PermissionDeniedError(status, error, message, headers, type);
}
if (status === 404) {
return new NotFoundError(status, error, message, headers, type);
}
if (status === 409) {
return new ConflictError(status, error, message, headers, type);
}
if (status === 422) {
return new UnprocessableEntityError(status, error, message, headers, type);
}
if (status === 429) {
return new RateLimitError(status, error, message, headers, type);
}
if (status >= 500) {
return new InternalServerError(status, error, message, headers, type);
}
return new APIError(status, error, message, headers, type);
}
}
export class APIUserAbortError extends APIError<undefined, undefined, undefined> {
constructor({ message }: { message?: string } = {}) {
super(undefined, undefined, message || 'Request was aborted.', undefined);
}
}
export class APIConnectionError extends APIError<undefined, undefined, undefined> {
constructor({ message, cause }: { message?: string | undefined; cause?: Error | undefined }) {
super(undefined, undefined, message || 'Connection error.', undefined);
// in some environments the 'cause' property is already declared
// @ts-ignore
if (cause) this.cause = cause;
}
}
export class APIConnectionTimeoutError extends APIConnectionError {
constructor({ message }: { message?: string } = {}) {
super({ message: message ?? 'Request timed out.' });
}
}
export class BadRequestError extends APIError<400, Headers> {}
export class AuthenticationError extends APIError<401, Headers> {}
export class PermissionDeniedError extends APIError<403, Headers> {}
export class NotFoundError extends APIError<404, Headers> {}
export class ConflictError extends APIError<409, Headers> {}
export class UnprocessableEntityError extends APIError<422, Headers> {}
export class RateLimitError extends APIError<429, Headers> {}
export class InternalServerError extends APIError<number, Headers> {}

View File

@@ -0,0 +1,331 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { AnthropicError } from './error';
import { FinalRequestOptions } from '../internal/request-options';
import { defaultParseResponse, WithRequestID } from '../internal/parse';
import { type BaseAnthropic } from '../client';
import { APIPromise } from './api-promise';
import { type APIResponseProps } from '../internal/parse';
import { maybeObj } from '../internal/utils/values';
export type PageRequestOptions = Pick<FinalRequestOptions, 'query' | 'headers' | 'body' | 'path' | 'method'>;
export abstract class AbstractPage<Item> implements AsyncIterable<Item> {
#client: BaseAnthropic;
protected options: FinalRequestOptions;
protected response: Response;
protected body: unknown;
constructor(client: BaseAnthropic, response: Response, body: unknown, options: FinalRequestOptions) {
this.#client = client;
this.options = options;
this.response = response;
this.body = body;
}
abstract nextPageRequestOptions(): PageRequestOptions | null;
abstract getPaginatedItems(): Item[];
hasNextPage(): boolean {
const items = this.getPaginatedItems();
if (!items.length) return false;
return this.nextPageRequestOptions() != null;
}
async getNextPage(): Promise<this> {
const nextOptions = this.nextPageRequestOptions();
if (!nextOptions) {
throw new AnthropicError(
'No next page expected; please check `.hasNextPage()` before calling `.getNextPage()`.',
);
}
return await this.#client.requestAPIList(this.constructor as any, nextOptions);
}
async *iterPages(): AsyncGenerator<this> {
let page: this = this;
yield page;
while (page.hasNextPage()) {
page = await page.getNextPage();
yield page;
}
}
async *[Symbol.asyncIterator](): AsyncGenerator<Item> {
for await (const page of this.iterPages()) {
for (const item of page.getPaginatedItems()) {
yield item;
}
}
}
}
/**
* This subclass of Promise will resolve to an instantiated Page once the request completes.
*
* It also implements AsyncIterable to allow auto-paginating iteration on an unawaited list call, eg:
*
* for await (const item of client.items.list()) {
* console.log(item)
* }
*/
export class PagePromise<
PageClass extends AbstractPage<Item>,
Item = ReturnType<PageClass['getPaginatedItems']>[number],
>
extends APIPromise<PageClass>
implements AsyncIterable<Item>
{
constructor(
client: BaseAnthropic,
request: Promise<APIResponseProps>,
Page: new (...args: ConstructorParameters<typeof AbstractPage>) => PageClass,
) {
super(
client,
request,
async (client, props) =>
new Page(
client,
props.response,
await defaultParseResponse(client, props),
props.options,
) as WithRequestID<PageClass>,
);
}
/**
* Allow auto-paginating iteration on an unawaited list call, eg:
*
* for await (const item of client.items.list()) {
* console.log(item)
* }
*/
async *[Symbol.asyncIterator](): AsyncGenerator<Item> {
const page = await this;
for await (const item of page) {
yield item;
}
}
}
export interface PageResponse<Item> {
data: Array<Item>;
has_more: boolean;
first_id: string | null;
last_id: string | null;
}
export interface PageParams {
/**
* Number of items per page.
*/
limit?: number;
before_id?: string;
after_id?: string;
}
export class Page<Item> extends AbstractPage<Item> implements PageResponse<Item> {
data: Array<Item>;
has_more: boolean;
first_id: string | null;
last_id: string | null;
constructor(
client: BaseAnthropic,
response: Response,
body: PageResponse<Item>,
options: FinalRequestOptions,
) {
super(client, response, body, options);
this.data = body.data || [];
this.has_more = body.has_more || false;
this.first_id = body.first_id || null;
this.last_id = body.last_id || null;
}
getPaginatedItems(): Item[] {
return this.data ?? [];
}
override hasNextPage(): boolean {
if (this.has_more === false) {
return false;
}
return super.hasNextPage();
}
nextPageRequestOptions(): PageRequestOptions | null {
if ((this.options.query as Record<string, unknown>)?.['before_id']) {
// in reverse
const first_id = this.first_id;
if (!first_id) {
return null;
}
return {
...this.options,
query: {
...maybeObj(this.options.query),
before_id: first_id,
},
};
}
const cursor = this.last_id;
if (!cursor) {
return null;
}
return {
...this.options,
query: {
...maybeObj(this.options.query),
after_id: cursor,
},
};
}
}
export interface TokenPageResponse<Item> {
data: Array<Item>;
has_more: boolean;
next_page: string | null;
}
export interface TokenPageParams {
/**
* Number of items per page.
*/
limit?: number;
page_token?: string;
}
export class TokenPage<Item> extends AbstractPage<Item> implements TokenPageResponse<Item> {
data: Array<Item>;
has_more: boolean;
next_page: string | null;
constructor(
client: BaseAnthropic,
response: Response,
body: TokenPageResponse<Item>,
options: FinalRequestOptions,
) {
super(client, response, body, options);
this.data = body.data || [];
this.has_more = body.has_more || false;
this.next_page = body.next_page || null;
}
getPaginatedItems(): Item[] {
return this.data ?? [];
}
override hasNextPage(): boolean {
if (this.has_more === false) {
return false;
}
return super.hasNextPage();
}
nextPageRequestOptions(): PageRequestOptions | null {
const cursor = this.next_page;
if (!cursor) {
return null;
}
return {
...this.options,
query: {
...maybeObj(this.options.query),
page_token: cursor,
},
};
}
}
export interface PageCursorResponse<Item> {
data: Array<Item>;
has_more: boolean;
next_page: string | null;
}
export interface PageCursorParams {
/**
* Number of items per page.
*/
limit?: number;
page?: string | null;
}
export class PageCursor<Item> extends AbstractPage<Item> implements PageCursorResponse<Item> {
data: Array<Item>;
has_more: boolean;
next_page: string | null;
constructor(
client: BaseAnthropic,
response: Response,
body: PageCursorResponse<Item>,
options: FinalRequestOptions,
) {
super(client, response, body, options);
this.data = body.data || [];
this.has_more = body.has_more || false;
this.next_page = body.next_page || null;
}
getPaginatedItems(): Item[] {
return this.data ?? [];
}
override hasNextPage(): boolean {
if (this.has_more === false) {
return false;
}
return super.hasNextPage();
}
nextPageRequestOptions(): PageRequestOptions | null {
const cursor = this.next_page;
if (!cursor) {
return null;
}
return {
...this.options,
query: {
...maybeObj(this.options.query),
page: cursor,
},
};
}
}

View File

@@ -0,0 +1,11 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { BaseAnthropic } from '../client';
export abstract class APIResource {
protected _client: BaseAnthropic;
constructor(client: BaseAnthropic) {
this._client = client;
}
}

View File

@@ -0,0 +1,348 @@
import { AnthropicError } from './error';
import { type ReadableStream } from '../internal/shim-types';
import { makeReadableStream } from '../internal/shims';
import { findDoubleNewlineIndex, LineDecoder } from '../internal/decoders/line';
import { ReadableStreamToAsyncIterable } from '../internal/shims';
import { isAbortError } from '../internal/errors';
import { safeJSON } from '../internal/utils/values';
import { encodeUTF8 } from '../internal/utils/bytes';
import { loggerFor } from '../internal/utils/log';
import type { BaseAnthropic } from '../client';
import { APIError } from './error';
import type { ErrorType } from '../resources/shared';
type Bytes = string | ArrayBuffer | Uint8Array | null | undefined;
export type ServerSentEvent = {
event: string | null;
data: string;
raw: string[];
};
export class Stream<Item> implements AsyncIterable<Item> {
controller: AbortController;
#client: BaseAnthropic | undefined;
constructor(
private iterator: () => AsyncIterator<Item>,
controller: AbortController,
client?: BaseAnthropic,
) {
this.controller = controller;
this.#client = client;
}
static fromSSEResponse<Item>(
response: Response,
controller: AbortController,
client?: BaseAnthropic,
): Stream<Item> {
let consumed = false;
const logger = client ? loggerFor(client) : console;
async function* iterator(): AsyncIterator<Item, any, undefined> {
if (consumed) {
throw new AnthropicError('Cannot iterate over a consumed stream, use `.tee()` to split the stream.');
}
consumed = true;
let done = false;
try {
for await (const sse of _iterSSEMessages(response, controller)) {
if (sse.event === 'completion') {
try {
yield JSON.parse(sse.data) as Item;
} catch (e) {
logger.error(`Could not parse message into JSON:`, sse.data);
logger.error(`From chunk:`, sse.raw);
throw e;
}
}
if (
sse.event === 'message_start' ||
sse.event === 'message_delta' ||
sse.event === 'message_stop' ||
sse.event === 'content_block_start' ||
sse.event === 'content_block_delta' ||
sse.event === 'content_block_stop'
) {
try {
yield JSON.parse(sse.data) as Item;
} catch (e) {
logger.error(`Could not parse message into JSON:`, sse.data);
logger.error(`From chunk:`, sse.raw);
throw e;
}
}
if (sse.event === 'ping') {
continue;
}
if (sse.event === 'error') {
const body = safeJSON(sse.data) ?? sse.data;
const type = body?.error?.type as ErrorType | undefined;
throw new APIError(undefined, body, undefined, response.headers, type);
}
}
done = true;
} catch (e) {
// If the user calls `stream.controller.abort()`, we should exit without throwing.
if (isAbortError(e)) return;
throw e;
} finally {
// If the user `break`s, abort the ongoing request.
if (!done) controller.abort();
}
}
return new Stream(iterator, controller, client);
}
/**
* Generates a Stream from a newline-separated ReadableStream
* where each item is a JSON value.
*/
static fromReadableStream<Item>(
readableStream: ReadableStream,
controller: AbortController,
client?: BaseAnthropic,
): Stream<Item> {
let consumed = false;
async function* iterLines(): AsyncGenerator<string, void, unknown> {
const lineDecoder = new LineDecoder();
const iter = ReadableStreamToAsyncIterable<Bytes>(readableStream);
for await (const chunk of iter) {
for (const line of lineDecoder.decode(chunk)) {
yield line;
}
}
for (const line of lineDecoder.flush()) {
yield line;
}
}
async function* iterator(): AsyncIterator<Item, any, undefined> {
if (consumed) {
throw new AnthropicError('Cannot iterate over a consumed stream, use `.tee()` to split the stream.');
}
consumed = true;
let done = false;
try {
for await (const line of iterLines()) {
if (done) continue;
if (line) yield JSON.parse(line) as Item;
}
done = true;
} catch (e) {
// If the user calls `stream.controller.abort()`, we should exit without throwing.
if (isAbortError(e)) return;
throw e;
} finally {
// If the user `break`s, abort the ongoing request.
if (!done) controller.abort();
}
}
return new Stream(iterator, controller, client);
}
[Symbol.asyncIterator](): AsyncIterator<Item> {
return this.iterator();
}
/**
* Splits the stream into two streams which can be
* independently read from at different speeds.
*/
tee(): [Stream<Item>, Stream<Item>] {
const left: Array<Promise<IteratorResult<Item>>> = [];
const right: Array<Promise<IteratorResult<Item>>> = [];
const iterator = this.iterator();
const teeIterator = (queue: Array<Promise<IteratorResult<Item>>>): AsyncIterator<Item> => {
return {
next: () => {
if (queue.length === 0) {
const result = iterator.next();
left.push(result);
right.push(result);
}
return queue.shift()!;
},
};
};
return [
new Stream(() => teeIterator(left), this.controller, this.#client),
new Stream(() => teeIterator(right), this.controller, this.#client),
];
}
/**
* Converts this stream to a newline-separated ReadableStream of
* JSON stringified values in the stream
* which can be turned back into a Stream with `Stream.fromReadableStream()`.
*/
toReadableStream(): ReadableStream {
const self = this;
let iter: AsyncIterator<Item>;
return makeReadableStream({
async start() {
iter = self[Symbol.asyncIterator]();
},
async pull(ctrl: any) {
try {
const { value, done } = await iter.next();
if (done) return ctrl.close();
const bytes = encodeUTF8(JSON.stringify(value) + '\n');
ctrl.enqueue(bytes);
} catch (err) {
ctrl.error(err);
}
},
async cancel() {
await iter.return?.();
},
});
}
}
export async function* _iterSSEMessages(
response: Response,
controller: AbortController,
): AsyncGenerator<ServerSentEvent, void, unknown> {
if (!response.body) {
controller.abort();
if (
typeof (globalThis as any).navigator !== 'undefined' &&
(globalThis as any).navigator.product === 'ReactNative'
) {
throw new AnthropicError(
`The default react-native fetch implementation does not support streaming. Please use expo/fetch: https://docs.expo.dev/versions/latest/sdk/expo/#expofetch-api`,
);
}
throw new AnthropicError(`Attempted to iterate over a response with no body`);
}
const sseDecoder = new SSEDecoder();
const lineDecoder = new LineDecoder();
const iter = ReadableStreamToAsyncIterable<Bytes>(response.body);
for await (const sseChunk of iterSSEChunks(iter)) {
for (const line of lineDecoder.decode(sseChunk)) {
const sse = sseDecoder.decode(line);
if (sse) yield sse;
}
}
for (const line of lineDecoder.flush()) {
const sse = sseDecoder.decode(line);
if (sse) yield sse;
}
}
/**
* Given an async iterable iterator, iterates over it and yields full
* SSE chunks, i.e. yields when a double new-line is encountered.
*/
async function* iterSSEChunks(iterator: AsyncIterableIterator<Bytes>): AsyncGenerator<Uint8Array> {
let data = new Uint8Array();
for await (const chunk of iterator) {
if (chunk == null) {
continue;
}
const binaryChunk =
chunk instanceof ArrayBuffer ? new Uint8Array(chunk)
: typeof chunk === 'string' ? encodeUTF8(chunk)
: chunk;
let newData = new Uint8Array(data.length + binaryChunk.length);
newData.set(data);
newData.set(binaryChunk, data.length);
data = newData;
let patternIndex;
while ((patternIndex = findDoubleNewlineIndex(data)) !== -1) {
yield data.slice(0, patternIndex);
data = data.slice(patternIndex);
}
}
if (data.length > 0) {
yield data;
}
}
class SSEDecoder {
private data: string[];
private event: string | null;
private chunks: string[];
constructor() {
this.event = null;
this.data = [];
this.chunks = [];
}
decode(line: string) {
if (line.endsWith('\r')) {
line = line.substring(0, line.length - 1);
}
if (!line) {
// empty line and we didn't previously encounter any messages
if (!this.event && !this.data.length) return null;
const sse: ServerSentEvent = {
event: this.event,
data: this.data.join('\n'),
raw: this.chunks,
};
this.event = null;
this.data = [];
this.chunks = [];
return sse;
}
this.chunks.push(line);
if (line.startsWith(':')) {
return null;
}
let [fieldname, _, value] = partition(line, ':');
if (value.startsWith(' ')) {
value = value.substring(1);
}
if (fieldname === 'event') {
this.event = value;
} else if (fieldname === 'data') {
this.data.push(value);
}
return null;
}
}
function partition(str: string, delimiter: string): [string, string, string] {
const index = str.indexOf(delimiter);
if (index !== -1) {
return [str.substring(0, index), delimiter, str.substring(index + delimiter.length)];
}
return [str, '', ''];
}

View File

@@ -0,0 +1,2 @@
export { type Uploadable } from '../internal/uploads';
export { toFile, type ToFileInput } from '../internal/to-file';

View File

@@ -0,0 +1,2 @@
/** @deprecated Import from ./core/error instead */
export * from './core/error';

View File

@@ -0,0 +1,75 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { Promisable, BetaRunnableTool } from '../../lib/tools/BetaRunnableTool';
import { BetaToolResultContentBlockParam } from '../../resources/beta';
import { AutoParseableBetaOutputFormat } from '../../lib/beta-parser';
import { AnthropicError } from '../..';
import { transformJSONSchema } from '../../lib/transform-json-schema';
type NoInfer<T> = T extends infer R ? R : never;
/**
* Creates a Tool with a provided JSON schema that can be passed
* to the `.toolRunner()` method. The schema is used to automatically validate
* the input arguments for the tool.
*/
export function betaTool<const Schema extends Exclude<JSONSchema, boolean> & { type: 'object' }>(options: {
name: string;
inputSchema: Schema;
description: string;
run: (args: NoInfer<FromSchema<Schema>>) => Promisable<string | Array<BetaToolResultContentBlockParam>>;
}): BetaRunnableTool<NoInfer<FromSchema<Schema>>> {
if (options.inputSchema.type !== 'object') {
throw new Error(
`JSON schema for tool "${options.name}" must be an object, but got ${options.inputSchema.type}`,
);
}
return {
type: 'custom',
name: options.name,
input_schema: options.inputSchema,
description: options.description,
run: options.run,
parse: (content: unknown) => content as FromSchema<Schema>,
} as any;
}
/**
* Creates a JSON schema output format object from the given JSON schema.
* If this is passed to the `.parse()` method then the response message will contain a
* `.parsed_output` property that is the result of parsing the content with the given JSON schema.
*
*/
export function betaJSONSchemaOutputFormat<
const Schema extends Exclude<JSONSchema, boolean> & { type: 'object' },
>(
jsonSchema: Schema,
options?: {
transform?: boolean;
},
): AutoParseableBetaOutputFormat<NoInfer<FromSchema<Schema>>> {
if (jsonSchema.type !== 'object') {
throw new Error(`JSON schema for tool must be an object, but got ${jsonSchema.type}`);
}
const transform = options?.transform ?? true;
if (transform) {
// todo: doing this is arguably necessary, but it does change the schema the user passed in
// so I'm not sure how we should handle that
jsonSchema = transformJSONSchema(jsonSchema) as Schema;
}
return {
type: 'json_schema',
schema: {
...jsonSchema,
},
parse: (content) => {
try {
return JSON.parse(content);
} catch (error) {
throw new AnthropicError(`Failed to parse structured output: ${error}`);
}
},
};
}

View File

@@ -0,0 +1,624 @@
/**
* Helper functions for integrating MCP (Model Context Protocol) SDK types
* with the Anthropic SDK.
*
* These helpers reduce boilerplate when converting between MCP types and
* Anthropic API types. The interfaces defined here use TypeScript's structural
* typing to match MCP SDK types without requiring a direct dependency.
*/
import { BetaRunnableTool } from '../../lib/tools/BetaRunnableTool';
import { ToolError } from '../../lib/tools/ToolError';
import {
BetaTool,
BetaToolResultContentBlockParam,
BetaMessageParam,
BetaTextBlockParam,
BetaImageBlockParam,
BetaRequestDocumentBlock,
BetaPlainTextSource,
} from '../../resources/beta';
import {
SDK_HELPER_SYMBOL,
collectStainlessHelpers,
stainlessHelperHeader,
} from '../../lib/stainless-helper-header';
import { fromBase64 } from '../../internal/utils/base64';
export { SDK_HELPER_SYMBOL, collectStainlessHelpers, stainlessHelperHeader };
// -----------------------------------------------------------------------------
// Minimal MCP interfaces (duck-typed to match MCP SDK without dependency)
// -----------------------------------------------------------------------------
/**
* Represents an MCP tool definition.
* Matches the shape returned by `mcpClient.listTools()`.
*/
export interface MCPToolLike {
name: string;
description?: string | undefined;
inputSchema: {
type: 'object';
properties?: Record<string, unknown> | null | undefined;
required?: string[] | readonly string[] | null | undefined;
[key: string]: unknown;
};
}
/**
* Represents the result of calling an MCP tool.
* Matches the shape returned by `mcpClient.callTool()`.
*/
export interface MCPCallToolResultLike {
content: MCPToolResultContentLike[];
structuredContent?: object | undefined;
isError?: boolean | undefined;
}
export type MCPToolResultContentLike =
| MCPTextContentLike
| MCPImageContentLike
| MCPAudioContentLike
| MCPEmbeddedResourceLike
| MCPResourceLinkLike;
export interface MCPTextContentLike {
type: 'text';
text: string;
}
export interface MCPImageContentLike {
type: 'image';
data: string;
mimeType: string;
}
export interface MCPAudioContentLike {
type: 'audio';
data: string;
mimeType: string;
}
export interface MCPEmbeddedResourceLike {
type: 'resource';
resource: MCPResourceContentsLike;
}
export interface MCPResourceLinkLike {
type: 'resource_link';
uri: string;
name: string;
mimeType?: string | undefined;
}
/**
* Text resource contents from MCP.
*/
export interface MCPTextResourceContentsLike {
uri: string;
mimeType?: string | undefined;
text: string;
}
/**
* Blob (binary) resource contents from MCP.
*/
export interface MCPBlobResourceContentsLike {
uri: string;
mimeType?: string | undefined;
blob: string;
}
/**
* Resource contents - either text or blob.
* Matches `TextResourceContents | BlobResourceContents` from MCP SDK.
*/
export type MCPResourceContentsLike = MCPTextResourceContentsLike | MCPBlobResourceContentsLike;
/**
* Interface for an MCP client that can call tools.
* Matches the relevant methods of `Client` from `@modelcontextprotocol/sdk`.
*/
export interface MCPClientLike {
callTool(params: { name: string; arguments?: Record<string, unknown> }): Promise<MCPCallToolResultLike>;
}
/**
* Represents a message from an MCP prompt.
* Matches the shape returned by `mcpClient.getPrompt()`.
*/
export interface MCPPromptMessageLike {
role: 'user' | 'assistant';
content: MCPPromptContentLike;
}
export type MCPPromptContentLike =
| MCPTextContentLike
| MCPImageContentLike
| MCPAudioContentLike
| MCPEmbeddedResourceLike
| MCPResourceLinkLike;
/**
* Represents the contents of an MCP resource.
* Matches the shape returned by `mcpClient.readResource()`.
*/
export interface MCPReadResourceResultLike {
contents: MCPResourceContentsLike[];
}
// -----------------------------------------------------------------------------
// Supported MIME types
// -----------------------------------------------------------------------------
const SUPPORTED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'] as const;
type SupportedImageType = (typeof SUPPORTED_IMAGE_TYPES)[number];
function isSupportedImageType(mimeType: string): mimeType is SupportedImageType {
return SUPPORTED_IMAGE_TYPES.includes(mimeType as SupportedImageType);
}
function isSupportedResourceMimeType(mimeType: string | undefined): boolean {
return (
!mimeType ||
mimeType.startsWith('text/') ||
mimeType === 'application/pdf' ||
isSupportedImageType(mimeType)
);
}
// -----------------------------------------------------------------------------
// Error classes
// -----------------------------------------------------------------------------
/**
* Error thrown when an MCP value cannot be converted to a format supported by the Claude API.
*/
export class UnsupportedMCPValueError extends Error {
constructor(message: string) {
super(message);
this.name = 'UnsupportedMCPValueError';
}
}
// -----------------------------------------------------------------------------
// Helper functions
// -----------------------------------------------------------------------------
/**
* Converts an MCP tool to a BetaRunnableTool for use with the Anthropic SDK's
* `toolRunner()` method.
*
* @param tool The MCP tool definition from `mcpClient.listTools()`
* @param mcpClient The MCP client instance used to call the tool
* @param extraProps Additional Claude API properties to include in the tool definition
* @returns A runnable tool for use with `anthropic.beta.messages.toolRunner()`
* @throws {UnsupportedMCPValueError} When the tool returns unsupported content types
* @throws {UnsupportedMCPValueError} When the tool returns unsupported resource links
* @throws {UnsupportedMCPValueError} When the tool returns resources with unsupported MIME types
*
* @example
* ```ts
* import { Client } from "@modelcontextprotocol/sdk/client/index.js";
* import Anthropic from "@anthropic-ai/sdk";
* import { mcpTool } from "@anthropic-ai/sdk/helpers/beta/mcp";
*
* const mcpClient = new Client({ name: "example", version: "1.0.0" });
* const anthropic = new Anthropic();
*
* const tools = await mcpClient.listTools();
* const runner = await anthropic.beta.messages.toolRunner({
* model: "claude-sonnet-4-20250514",
* max_tokens: 1024,
* tools: tools.tools.map(tool => mcpTool(tool, mcpClient)),
* messages: [{ role: "user", content: "Use the available tools" }],
* });
* ```
*/
export function mcpTool(
tool: MCPToolLike,
mcpClient: MCPClientLike,
extraProps?: Partial<Omit<BetaTool, 'name' | 'description' | 'input_schema'>>,
): BetaRunnableTool<Record<string, unknown>> {
// Transform inputSchema to match BetaTool.InputSchema (convert undefined to null)
const inputSchema: BetaTool['input_schema'] = {
...tool.inputSchema,
type: 'object',
properties: tool.inputSchema.properties ?? null,
required: tool.inputSchema.required ?? null,
};
const betaTool: BetaTool = {
name: tool.name,
input_schema: inputSchema,
...(tool.description !== undefined ? { description: tool.description } : {}),
...extraProps,
};
const runnableTool = {
...betaTool,
run: async (input: Record<string, unknown>): Promise<string | Array<BetaToolResultContentBlockParam>> => {
const result = await mcpClient.callTool({
name: tool.name,
arguments: input,
});
if (result.isError) {
const content = result.content.map((item) => mcpContent(item));
throw new ToolError(content);
}
// If content is empty but structuredContent is present, JSON encode it
// Spec: "For backwards compatibility, a tool that returns structured content SHOULD also return the serialized JSON in a TextContent block."
// meaning it's not required and cannot be assumed.
if (
result.content.length === 0 &&
// Spec: "Structured content is returned as a JSON object in the structuredContent field of a result."
typeof result.structuredContent === 'object' &&
result.structuredContent !== null
) {
return JSON.stringify(result.structuredContent);
}
return result.content.map((item) => mcpContent(item));
},
parse: (content: unknown): Record<string, unknown> => content as Record<string, unknown>,
[SDK_HELPER_SYMBOL]: 'mcpTool',
};
return runnableTool;
}
/**
* Converts an array of MCP tools to BetaRunnableTools.
*
* @param tools Array of MCP tool definitions from `mcpClient.listTools()`
* @param mcpClient The MCP client instance used to call the tools
* @param extraProps Additional Claude API properties to include in each tool definition
* @returns An array of runnable tools for use with `anthropic.beta.messages.toolRunner()`
*
* @example
* ```ts
* const { tools } = await mcpClient.listTools();
* const runner = await anthropic.beta.messages.toolRunner({
* model: "claude-sonnet-4-20250514",
* max_tokens: 1024,
* tools: mcpTools(tools, mcpClient),
* messages: [{ role: "user", content: "Use the available tools" }],
* });
* ```
*/
export function mcpTools(
tools: MCPToolLike[],
mcpClient: MCPClientLike,
extraProps?: Partial<Omit<BetaTool, 'name' | 'description' | 'input_schema'>>,
): BetaRunnableTool<Record<string, unknown>>[] {
return tools.map((tool) => mcpTool(tool, mcpClient, extraProps));
}
/**
* Converts an MCP prompt message to an Anthropic BetaMessageParam.
*
* @param mcpMessage The MCP prompt message from `mcpClient.getPrompt()`
* @param extraProps Additional Claude API properties to include in content blocks (e.g., `cache_control`)
* @returns A message parameter for use with `anthropic.beta.messages.create()`
* @throws {UnsupportedMCPValueError} When the message contains unsupported content types
* @throws {UnsupportedMCPValueError} When the message contains unsupported resource links
* @throws {UnsupportedMCPValueError} When the message contains resources with unsupported MIME types
*
* @example
* ```ts
* import { Client } from "@modelcontextprotocol/sdk/client/index.js";
* import Anthropic from "@anthropic-ai/sdk";
* import { mcpMessage } from "@anthropic-ai/sdk/helpers/beta/mcp";
*
* const mcpClient = new Client({ name: "example", version: "1.0.0" });
* const anthropic = new Anthropic();
*
* const prompt = await mcpClient.getPrompt({
* name: "example-prompt",
* arguments: { arg1: "value" },
* });
*
* await anthropic.beta.messages.create({
* model: "claude-sonnet-4-20250514",
* max_tokens: 1024,
* messages: prompt.messages.map(msg => mcpMessage(msg)),
* });
* ```
*/
export function mcpMessage(
mcpMessage: MCPPromptMessageLike,
extraProps?: Partial<
Omit<BetaTextBlockParam, 'type' | 'text' | 'source'> &
Omit<BetaImageBlockParam, 'type' | 'source'> &
Omit<BetaRequestDocumentBlock, 'type' | 'source'>
>,
): BetaMessageParam {
const message = {
role: mcpMessage.role,
content: [mcpContent(mcpMessage.content, extraProps)],
[SDK_HELPER_SYMBOL]: 'mcpMessage',
};
return message;
}
/**
* Converts an array of MCP prompt messages to Anthropic BetaMessageParams.
*
* @param messages Array of MCP prompt messages from `mcpClient.getPrompt()`
* @param extraProps Additional Claude API properties to include in content blocks (e.g., `cache_control`)
* @returns An array of message parameters for use with `anthropic.beta.messages.create()`
* @throws {UnsupportedMCPValueError} When any message contains unsupported content types
* @throws {UnsupportedMCPValueError} When any message contains unsupported resource links
* @throws {UnsupportedMCPValueError} When any message contains resources with unsupported MIME types
*
* @example
* ```ts
* const { messages } = await mcpClient.getPrompt({ name: "example-prompt" });
* await anthropic.beta.messages.create({
* model: "claude-sonnet-4-20250514",
* max_tokens: 1024,
* messages: mcpMessages(messages),
* });
* ```
*/
export function mcpMessages(
messages: MCPPromptMessageLike[],
extraProps?: Partial<
Omit<BetaTextBlockParam, 'type' | 'text' | 'source'> &
Omit<BetaImageBlockParam, 'type' | 'source'> &
Omit<BetaRequestDocumentBlock, 'type' | 'source'>
>,
): BetaMessageParam[] {
return messages.map((message) => mcpMessage(message, extraProps));
}
/**
* Converts a single MCP prompt content item to an Anthropic content block.
*
* @param content The MCP content item (text, image, or embedded resource)
* @param extraProps Additional Claude API properties to include in the content block (e.g., `cache_control`)
* @returns A Claude content block for use in a message's content array
* @throws {UnsupportedMCPValueError} When the content type is not supported (e.g., 'audio')
* @throws {UnsupportedMCPValueError} When resource links use non-http/https protocols
* @throws {UnsupportedMCPValueError} When resources have unsupported MIME types
*
* @example
* ```ts
* const { messages } = await mcpClient.getPrompt({ name: "my-prompt" });
* // If you need to mix MCP content with other content:
* await anthropic.beta.messages.create({
* model: "claude-sonnet-4-20250514",
* max_tokens: 1024,
* messages: [{
* role: "user",
* content: [
* mcpContent(messages[0].content),
* { type: "text", text: "Additional context" },
* ],
* }],
* });
* ```
*/
export function mcpContent(
content: MCPPromptContentLike,
extraProps?: Partial<
Omit<BetaTextBlockParam, 'type' | 'text' | 'source'> &
Omit<BetaImageBlockParam, 'type' | 'source'> &
Omit<BetaRequestDocumentBlock, 'type' | 'source'>
>,
): BetaTextBlockParam | BetaImageBlockParam | BetaRequestDocumentBlock {
switch (content.type) {
case 'text': {
const textBlock = {
type: 'text' as const,
text: content.text,
...extraProps,
[SDK_HELPER_SYMBOL]: 'mcpContent',
};
return textBlock;
}
case 'image': {
if (!isSupportedImageType(content.mimeType)) {
throw new UnsupportedMCPValueError(`Unsupported image MIME type: ${content.mimeType}`);
}
const imageBlock = {
type: 'image' as const,
source: {
type: 'base64' as const,
data: content.data,
media_type: content.mimeType,
},
...extraProps,
[SDK_HELPER_SYMBOL]: 'mcpContent',
};
return imageBlock;
}
case 'resource':
return mcpResourceContentToContentBlock(content.resource, extraProps, 'mcpContent');
case 'resource_link':
case 'audio':
throw new UnsupportedMCPValueError(`Unsupported MCP content type: ${content.type}`);
default:
// This should never happen as we handle all MCPPromptContentLike types
content satisfies never;
throw new UnsupportedMCPValueError(
`Unsupported MCP content type: ${(content as { type: string }).type}`,
);
}
}
/**
* Converts a single MCP resource contents item to an Anthropic content block.
*/
function mcpResourceContentToContentBlock(
resourceContent: MCPResourceContentsLike,
extraProps?: Partial<Omit<BetaRequestDocumentBlock, 'type' | 'source'>>,
helperName: string = 'mcpResourceToContent',
): BetaTextBlockParam | BetaImageBlockParam | BetaRequestDocumentBlock {
const mimeType = resourceContent.mimeType;
// Handle images (requires blob - base64-encoded binary data)
if (mimeType && isSupportedImageType(mimeType)) {
if (!('blob' in resourceContent)) {
throw new UnsupportedMCPValueError(
`Image resource must have blob data, not text. URI: ${resourceContent.uri}`,
);
}
const imageBlock = {
type: 'image' as const,
source: {
type: 'base64' as const,
data: resourceContent.blob,
media_type: mimeType,
},
...extraProps,
[SDK_HELPER_SYMBOL]: helperName,
};
return imageBlock;
}
// Handle PDFs (requires blob - base64-encoded binary data)
if (mimeType === 'application/pdf') {
if (!('blob' in resourceContent)) {
throw new UnsupportedMCPValueError(
`PDF resource must have blob data, not text. URI: ${resourceContent.uri}`,
);
}
const pdfBlock = {
type: 'document' as const,
source: {
type: 'base64' as const,
data: resourceContent.blob,
media_type: 'application/pdf' as const,
},
...extraProps,
[SDK_HELPER_SYMBOL]: helperName,
};
return pdfBlock;
}
// Handle text types (text/*, or no MIME type defaults to text)
if (!mimeType || mimeType.startsWith('text/')) {
const textDocBlock = {
type: 'document' as const,
source: textSourceFromResource(resourceContent),
...extraProps,
[SDK_HELPER_SYMBOL]: helperName,
};
return textDocBlock;
}
throw new UnsupportedMCPValueError(
`Unsupported MIME type "${mimeType}" for resource: ${resourceContent.uri}`,
);
}
/**
* Converts MCP resource contents to an Anthropic content block.
*
* This helper is useful when you have resource contents from `mcpClient.readResource()`
* and want to include them in a message or as a document source. It automatically
* finds the first resource with a supported MIME type.
*
* @param result The result from `mcpClient.readResource()`
* @param extraProps Additional Claude API properties to include in the content block (e.g., `cache_control`)
* @returns A Claude content block
* @throws {UnsupportedMCPValueError} When contents array is empty or none have a supported MIME type
*
* @example
* ```ts
* import { Client } from "@modelcontextprotocol/sdk/client/index.js";
* import Anthropic from "@anthropic-ai/sdk";
* import { mcpResourceToContent } from "@anthropic-ai/sdk/helpers/beta/mcp";
*
* const mcpClient = new Client({ name: "example", version: "1.0.0" });
* const anthropic = new Anthropic();
*
* const resource = await mcpClient.readResource({ uri: "file:///example.txt" });
* await anthropic.beta.messages.create({
* model: "claude-sonnet-4-20250514",
* max_tokens: 1024,
* messages: [{
* role: "user",
* content: [mcpResourceToContent(resource)],
* }],
* });
* ```
*/
export function mcpResourceToContent(
result: MCPReadResourceResultLike,
extraProps?: Partial<Omit<BetaRequestDocumentBlock, 'type' | 'source'>>,
): BetaTextBlockParam | BetaImageBlockParam | BetaRequestDocumentBlock {
if (result.contents.length === 0) {
throw new UnsupportedMCPValueError('Resource contents array must contain at least one item');
}
const supported = result.contents.find((c) => isSupportedResourceMimeType(c.mimeType));
if (!supported) {
const mimeTypes = result.contents.map((c) => c.mimeType).filter((m) => m !== undefined);
throw new UnsupportedMCPValueError(
`No supported MIME type found in resource contents. Available: ${mimeTypes.join(', ')}`,
);
}
return mcpResourceContentToContentBlock(supported, extraProps);
}
/**
* Gets the raw bytes from an MCP resource.
*/
function bytesFromResource(resource: MCPResourceContentsLike): Uint8Array {
if ('blob' in resource) {
return fromBase64(resource.blob);
}
return new TextEncoder().encode(resource.text);
}
/**
* Creates a text document source from an MCP resource, decoding base64 blob to UTF-8 if needed.
*/
function textSourceFromResource(resource: MCPResourceContentsLike): BetaPlainTextSource {
const data = 'text' in resource ? resource.text : new TextDecoder().decode(fromBase64(resource.blob));
return { type: 'text', data, media_type: 'text/plain' };
}
/**
* Converts an MCP resource to a File object suitable for uploading via `anthropic.beta.files.upload()`.
*
* @param result The result from `mcpClient.readResource()`
* @returns A File object for use with `anthropic.beta.files.upload()`
* @throws {UnsupportedMCPValueError} When contents array is empty
*
* @example
* ```ts
* import { Client } from "@modelcontextprotocol/sdk/client/index.js";
* import Anthropic from "@anthropic-ai/sdk";
* import { mcpResourceToFile } from "@anthropic-ai/sdk/helpers/beta/mcp";
*
* const mcpClient = new Client({ name: "example", version: "1.0.0" });
* const anthropic = new Anthropic();
*
* const resource = await mcpClient.readResource({ uri: "file:///document.pdf" });
*
* const uploaded = await anthropic.beta.files.upload({
* file: mcpResourceToFile(resource),
* });
* ```
*/
export function mcpResourceToFile(result: MCPReadResourceResultLike): File {
if (result.contents.length === 0) {
throw new UnsupportedMCPValueError('Resource contents array must contain at least one item');
}
const resourceContents = result.contents[0]!;
const name = new URL(resourceContents.uri).pathname.split('/').at(-1) || 'file';
const type = resourceContents.mimeType;
const data = bytesFromResource(resourceContents);
const file = new File([data], name, type ? { type } : undefined);
(file as any)[SDK_HELPER_SYMBOL] = 'mcpResourceToFile';
return file;
}

View File

@@ -0,0 +1,28 @@
import { BetaRunnableTool, Promisable } from '../../lib/tools/BetaRunnableTool';
import { BetaMemoryTool20250818Command, BetaToolResultContentBlockParam } from '../../resources/beta';
type Command = BetaMemoryTool20250818Command['command'];
export type MemoryToolHandlers = {
[K in Command]: (
command: Extract<BetaMemoryTool20250818Command, { command: K }>,
) => Promisable<string | Array<BetaToolResultContentBlockParam>>;
};
export function betaMemoryTool(
handlers: MemoryToolHandlers,
): BetaRunnableTool<BetaMemoryTool20250818Command> {
return {
type: 'memory_20250818',
name: 'memory',
parse: (content) => content as BetaMemoryTool20250818Command,
run: (args) => {
const handler = handlers[args.command];
if (!handler) {
throw new Error(`${args.command} not implemented`);
}
return handler.bind(handlers)(args as any);
},
};
}

View File

@@ -0,0 +1,72 @@
import { transformJSONSchema } from '../..//lib/transform-json-schema';
import type { infer as zodInfer, ZodType } from 'zod';
import * as z from 'zod/v4';
import { AnthropicError } from '../../core/error';
import { AutoParseableBetaOutputFormat } from '../../lib/beta-parser';
import { BetaRunnableTool, Promisable } from '../../lib/tools/BetaRunnableTool';
import { BetaToolResultContentBlockParam } from '../../resources/beta';
/**
* Creates a JSON schema output format object from the given Zod schema.
*
* If this is passed to the `.parse()` method then the response message will contain a
* `.parsed_output` property that is the result of parsing the content with the given Zod object.
*
* This can be passed directly to the `.create()` method but will not
* result in any automatic parsing, you'll have to parse the response yourself.
*/
export function betaZodOutputFormat<ZodInput extends ZodType>(
zodObject: ZodInput,
): AutoParseableBetaOutputFormat<zodInfer<ZodInput>> {
let jsonSchema = z.toJSONSchema(zodObject, { reused: 'ref' });
jsonSchema = transformJSONSchema(jsonSchema);
return {
type: 'json_schema',
schema: {
...jsonSchema,
},
parse: (content) => {
const output = zodObject.safeParse(JSON.parse(content));
if (!output.success) {
throw new AnthropicError(
`Failed to parse structured output: ${output.error.message} cause: ${output.error.issues}`,
);
}
return output.data;
},
};
}
/**
* Creates a tool using the provided Zod schema that can be passed
* into the `.toolRunner()` method. The Zod schema will automatically be
* converted into JSON Schema when passed to the API. The provided function's
* input arguments will also be validated against the provided schema.
*/
export function betaZodTool<InputSchema extends ZodType>(options: {
name: string;
inputSchema: InputSchema;
description: string;
run: (args: zodInfer<InputSchema>) => Promisable<string | Array<BetaToolResultContentBlockParam>>;
}): BetaRunnableTool<zodInfer<InputSchema>> {
const jsonSchema = z.toJSONSchema(options.inputSchema, { reused: 'ref' });
if (jsonSchema.type !== 'object') {
throw new Error(`Zod schema for tool "${options.name}" must be an object, but got ${jsonSchema.type}`);
}
// TypeScript doesn't narrow the type after the runtime check, so we need to assert it
const objectSchema = jsonSchema as typeof jsonSchema & { type: 'object' };
return {
type: 'custom',
name: options.name,
input_schema: objectSchema,
description: options.description,
run: options.run,
parse: (args: unknown) => options.inputSchema.parse(args) as zodInfer<InputSchema>,
};
}

View File

@@ -0,0 +1,2 @@
export { jsonSchemaOutputFormat } from './json-schema';
export { zodOutputFormat } from './zod';

View File

@@ -0,0 +1,48 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { AutoParseableOutputFormat } from '../lib/parser';
import { AnthropicError } from '../core/error';
import { transformJSONSchema } from '../lib/transform-json-schema';
type NoInfer<T> = T extends infer R ? R : never;
/**
* Creates a JSON schema output format object from the given JSON schema.
* If this is passed to the `.parse()` method then the response message will contain a
* `.parsed_output` property that is the result of parsing the content with the given JSON schema.
*
* Note: When `transform` is enabled (the default), the schema is deep-cloned before transformation,
* so the original schema object is not modified.
*/
export function jsonSchemaOutputFormat<
const Schema extends Exclude<JSONSchema, boolean> & { type: 'object' },
>(
jsonSchema: Schema,
options?: {
transform?: boolean;
},
): AutoParseableOutputFormat<NoInfer<FromSchema<Schema>>> {
if (jsonSchema.type !== 'object') {
throw new Error(`JSON schema must be an object, but got ${jsonSchema.type}`);
}
const transform = options?.transform ?? true;
if (transform) {
jsonSchema = transformJSONSchema(jsonSchema) as Schema;
}
return {
type: 'json_schema',
schema: {
...jsonSchema,
},
parse: (content) => {
try {
return JSON.parse(content);
} catch (error) {
throw new AnthropicError(
`Failed to parse structured output: ${error instanceof Error ? error.message : String(error)}`,
);
}
},
};
}

View File

@@ -0,0 +1,58 @@
import { transformJSONSchema } from '../lib/transform-json-schema';
import type { infer as zodInfer, ZodType } from 'zod';
import * as z from 'zod/v4';
import { AnthropicError } from '../core/error';
import { AutoParseableOutputFormat } from '../lib/parser';
/**
* Creates a JSON schema output format object from the given Zod schema.
*
* If this is passed to the `.parse()` method then the response message will contain a
* `.parsed_output` property that is the result of parsing the content with the given Zod object.
*
* This can be passed directly to the `.create()` method but will not
* result in any automatic parsing, you'll have to parse the response yourself.
*/
export function zodOutputFormat<ZodInput extends ZodType>(
zodObject: ZodInput,
): AutoParseableOutputFormat<zodInfer<ZodInput>> {
let jsonSchema = z.toJSONSchema(zodObject, { reused: 'ref' });
jsonSchema = transformJSONSchema(jsonSchema);
return {
type: 'json_schema',
schema: {
...jsonSchema,
},
parse: (content) => {
let parsed: unknown;
try {
parsed = JSON.parse(content);
} catch (error) {
throw new AnthropicError(
`Failed to parse structured output as JSON: ${
error instanceof Error ? error.message : String(error)
}`,
);
}
const output = zodObject.safeParse(parsed);
if (!output.success) {
const formattedIssues = output.error.issues
.slice(0, 5)
.map((issue) => ` - ${issue.path.join('.')}: ${issue.message}`)
.join('\n');
const issueCount = output.error.issues.length;
const suffix = issueCount > 5 ? `\n ... and ${issueCount - 5} more issue(s)` : '';
throw new AnthropicError(
`Failed to parse structured output: ${output.error.message}\nValidation issues:\n${formattedIssues}${suffix}`,
);
}
return output.data;
},
};
}

View File

@@ -0,0 +1,31 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export { Anthropic as default } from './client';
export { type Uploadable, toFile } from './core/uploads';
export { APIPromise } from './core/api-promise';
export { BaseAnthropic, Anthropic, type ClientOptions, HUMAN_PROMPT, AI_PROMPT } from './client';
export { PagePromise } from './core/pagination';
export {
AnthropicError,
APIError,
APIConnectionError,
APIConnectionTimeoutError,
APIUserAbortError,
NotFoundError,
ConflictError,
RateLimitError,
BadRequestError,
AuthenticationError,
InternalServerError,
PermissionDeniedError,
UnprocessableEntityError,
} from './core/error';
export type {
AutoParseableOutputFormat,
ParsedMessage,
ParsedContentBlock,
ParseableMessageCreateParams,
ExtractParsedContentFromParams,
} from './lib/parser';

View File

@@ -0,0 +1,3 @@
# `internal`
The modules in this directory are not importable outside this package and will change between releases.

View File

@@ -0,0 +1,93 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export type Fetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
/**
* An alias to the builtin `RequestInit` type so we can
* easily alias it in import statements if there are name clashes.
*
* https://developer.mozilla.org/docs/Web/API/RequestInit
*/
type _RequestInit = RequestInit;
/**
* An alias to the builtin `Response` type so we can
* easily alias it in import statements if there are name clashes.
*
* https://developer.mozilla.org/docs/Web/API/Response
*/
type _Response = Response;
/**
* The type for the first argument to `fetch`.
*
* https://developer.mozilla.org/docs/Web/API/Window/fetch#resource
*/
type _RequestInfo = Request | URL | string;
/**
* The type for constructing `RequestInit` Headers.
*
* https://developer.mozilla.org/docs/Web/API/RequestInit#setting_headers
*/
type _HeadersInit = RequestInit['headers'];
/**
* The type for constructing `RequestInit` body.
*
* https://developer.mozilla.org/docs/Web/API/RequestInit#body
*/
type _BodyInit = RequestInit['body'];
/**
* An alias to the builtin `Array<T>` type so we can
* easily alias it in import statements if there are name clashes.
*/
type _Array<T> = Array<T>;
/**
* An alias to the builtin `Record<K, T>` type so we can
* easily alias it in import statements if there are name clashes.
*/
type _Record<K extends keyof any, T> = Record<K, T>;
export type {
_Array as Array,
_BodyInit as BodyInit,
_HeadersInit as HeadersInit,
_Record as Record,
_RequestInfo as RequestInfo,
_RequestInit as RequestInit,
_Response as Response,
};
/**
* A copy of the builtin `EndingType` type as it isn't fully supported in certain
* environments and attempting to reference the global version will error.
*
* https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L27941
*/
type EndingType = 'native' | 'transparent';
/**
* A copy of the builtin `BlobPropertyBag` type as it isn't fully supported in certain
* environments and attempting to reference the global version will error.
*
* https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L154
* https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob#options
*/
export interface BlobPropertyBag {
endings?: EndingType;
type?: string;
}
/**
* A copy of the builtin `FilePropertyBag` type as it isn't fully supported in certain
* environments and attempting to reference the global version will error.
*
* https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L503
* https://developer.mozilla.org/en-US/docs/Web/API/File/File#options
*/
export interface FilePropertyBag extends BlobPropertyBag {
lastModified?: number;
}

View File

@@ -0,0 +1,15 @@
// File containing shared constants
/**
* Model-specific timeout constraints for non-streaming requests
*/
export const MODEL_NONSTREAMING_TOKENS: Record<string, number> = {
'claude-opus-4-20250514': 8192,
'claude-opus-4-0': 8192,
'claude-4-opus-20250514': 8192,
'anthropic.claude-opus-4-20250514-v1:0': 8192,
'claude-opus-4@20250514': 8192,
'claude-opus-4-1-20250805': 8192,
'anthropic.claude-opus-4-1-20250805-v1:0': 8192,
'claude-opus-4-1@20250805': 8192,
};

View File

@@ -0,0 +1,48 @@
import { AnthropicError } from '../../core/error';
import { ReadableStreamToAsyncIterable } from '../shims';
import { LineDecoder, type Bytes } from './line';
export class JSONLDecoder<T> {
controller: AbortController;
constructor(
private iterator: AsyncIterableIterator<Bytes>,
controller: AbortController,
) {
this.controller = controller;
}
private async *decoder(): AsyncIterator<T, any, undefined> {
const lineDecoder = new LineDecoder();
for await (const chunk of this.iterator) {
for (const line of lineDecoder.decode(chunk)) {
yield JSON.parse(line) as T;
}
}
for (const line of lineDecoder.flush()) {
yield JSON.parse(line) as T;
}
}
[Symbol.asyncIterator](): AsyncIterator<T> {
return this.decoder();
}
static fromResponse<T>(response: Response, controller: AbortController): JSONLDecoder<T> {
if (!response.body) {
controller.abort();
if (
typeof (globalThis as any).navigator !== 'undefined' &&
(globalThis as any).navigator.product === 'ReactNative'
) {
throw new AnthropicError(
`The default react-native fetch implementation does not support streaming. Please use expo/fetch: https://docs.expo.dev/versions/latest/sdk/expo/#expofetch-api`,
);
}
throw new AnthropicError(`Attempted to iterate over a response with no body`);
}
return new JSONLDecoder(ReadableStreamToAsyncIterable<Bytes>(response.body), controller);
}
}

View File

@@ -0,0 +1,135 @@
import { concatBytes, decodeUTF8, encodeUTF8 } from '../utils/bytes';
export type Bytes = string | ArrayBuffer | Uint8Array | null | undefined;
/**
* A re-implementation of httpx's `LineDecoder` in Python that handles incrementally
* reading lines from text.
*
* https://github.com/encode/httpx/blob/920333ea98118e9cf617f246905d7b202510941c/httpx/_decoders.py#L258
*/
export class LineDecoder {
// prettier-ignore
static NEWLINE_CHARS = new Set(['\n', '\r']);
static NEWLINE_REGEXP = /\r\n|[\n\r]/g;
#buffer: Uint8Array;
#carriageReturnIndex: number | null;
constructor() {
this.#buffer = new Uint8Array();
this.#carriageReturnIndex = null;
}
decode(chunk: Bytes): string[] {
if (chunk == null) {
return [];
}
const binaryChunk =
chunk instanceof ArrayBuffer ? new Uint8Array(chunk)
: typeof chunk === 'string' ? encodeUTF8(chunk)
: chunk;
this.#buffer = concatBytes([this.#buffer, binaryChunk]);
const lines: string[] = [];
let patternIndex;
while ((patternIndex = findNewlineIndex(this.#buffer, this.#carriageReturnIndex)) != null) {
if (patternIndex.carriage && this.#carriageReturnIndex == null) {
// skip until we either get a corresponding `\n`, a new `\r` or nothing
this.#carriageReturnIndex = patternIndex.index;
continue;
}
// we got double \r or \rtext\n
if (
this.#carriageReturnIndex != null &&
(patternIndex.index !== this.#carriageReturnIndex + 1 || patternIndex.carriage)
) {
lines.push(decodeUTF8(this.#buffer.subarray(0, this.#carriageReturnIndex - 1)));
this.#buffer = this.#buffer.subarray(this.#carriageReturnIndex);
this.#carriageReturnIndex = null;
continue;
}
const endIndex =
this.#carriageReturnIndex !== null ? patternIndex.preceding - 1 : patternIndex.preceding;
const line = decodeUTF8(this.#buffer.subarray(0, endIndex));
lines.push(line);
this.#buffer = this.#buffer.subarray(patternIndex.index);
this.#carriageReturnIndex = null;
}
return lines;
}
flush(): string[] {
if (!this.#buffer.length) {
return [];
}
return this.decode('\n');
}
}
/**
* This function searches the buffer for the end patterns, (\r or \n)
* and returns an object with the index preceding the matched newline and the
* index after the newline char. `null` is returned if no new line is found.
*
* ```ts
* findNewLineIndex('abc\ndef') -> { preceding: 2, index: 3 }
* ```
*/
function findNewlineIndex(
buffer: Uint8Array,
startIndex: number | null,
): { preceding: number; index: number; carriage: boolean } | null {
const newline = 0x0a; // \n
const carriage = 0x0d; // \r
for (let i = startIndex ?? 0; i < buffer.length; i++) {
if (buffer[i] === newline) {
return { preceding: i, index: i + 1, carriage: false };
}
if (buffer[i] === carriage) {
return { preceding: i, index: i + 1, carriage: true };
}
}
return null;
}
export function findDoubleNewlineIndex(buffer: Uint8Array): number {
// This function searches the buffer for the end patterns (\r\r, \n\n, \r\n\r\n)
// and returns the index right after the first occurrence of any pattern,
// or -1 if none of the patterns are found.
const newline = 0x0a; // \n
const carriage = 0x0d; // \r
for (let i = 0; i < buffer.length - 1; i++) {
if (buffer[i] === newline && buffer[i + 1] === newline) {
// \n\n
return i + 2;
}
if (buffer[i] === carriage && buffer[i + 1] === carriage) {
// \r\r
return i + 2;
}
if (
buffer[i] === carriage &&
buffer[i + 1] === newline &&
i + 3 < buffer.length &&
buffer[i + 2] === carriage &&
buffer[i + 3] === newline
) {
// \r\n\r\n
return i + 4;
}
}
return -1;
}

View File

@@ -0,0 +1,196 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { VERSION } from '../version';
export const isRunningInBrowser = () => {
return (
// @ts-ignore
typeof window !== 'undefined' &&
// @ts-ignore
typeof window.document !== 'undefined' &&
// @ts-ignore
typeof navigator !== 'undefined'
);
};
type DetectedPlatform = 'deno' | 'node' | 'edge' | 'unknown';
/**
* Note this does not detect 'browser'; for that, use getBrowserInfo().
*/
function getDetectedPlatform(): DetectedPlatform {
if (typeof Deno !== 'undefined' && Deno.build != null) {
return 'deno';
}
if (typeof EdgeRuntime !== 'undefined') {
return 'edge';
}
if (
Object.prototype.toString.call(
typeof (globalThis as any).process !== 'undefined' ? (globalThis as any).process : 0,
) === '[object process]'
) {
return 'node';
}
return 'unknown';
}
declare const Deno: any;
declare const EdgeRuntime: any;
type Arch = 'x32' | 'x64' | 'arm' | 'arm64' | `other:${string}` | 'unknown';
type PlatformName =
| 'MacOS'
| 'Linux'
| 'Windows'
| 'FreeBSD'
| 'OpenBSD'
| 'iOS'
| 'Android'
| `Other:${string}`
| 'Unknown';
type Browser = 'ie' | 'edge' | 'chrome' | 'firefox' | 'safari';
type PlatformProperties = {
'X-Stainless-Lang': 'js';
'X-Stainless-Package-Version': string;
'X-Stainless-OS': PlatformName;
'X-Stainless-Arch': Arch;
'X-Stainless-Runtime': 'node' | 'deno' | 'edge' | `browser:${Browser}` | 'unknown';
'X-Stainless-Runtime-Version': string;
};
const getPlatformProperties = (): PlatformProperties => {
const detectedPlatform = getDetectedPlatform();
if (detectedPlatform === 'deno') {
return {
'X-Stainless-Lang': 'js',
'X-Stainless-Package-Version': VERSION,
'X-Stainless-OS': normalizePlatform(Deno.build.os),
'X-Stainless-Arch': normalizeArch(Deno.build.arch),
'X-Stainless-Runtime': 'deno',
'X-Stainless-Runtime-Version':
typeof Deno.version === 'string' ? Deno.version : Deno.version?.deno ?? 'unknown',
};
}
if (typeof EdgeRuntime !== 'undefined') {
return {
'X-Stainless-Lang': 'js',
'X-Stainless-Package-Version': VERSION,
'X-Stainless-OS': 'Unknown',
'X-Stainless-Arch': `other:${EdgeRuntime}`,
'X-Stainless-Runtime': 'edge',
'X-Stainless-Runtime-Version': (globalThis as any).process.version,
};
}
// Check if Node.js
if (detectedPlatform === 'node') {
return {
'X-Stainless-Lang': 'js',
'X-Stainless-Package-Version': VERSION,
'X-Stainless-OS': normalizePlatform((globalThis as any).process.platform ?? 'unknown'),
'X-Stainless-Arch': normalizeArch((globalThis as any).process.arch ?? 'unknown'),
'X-Stainless-Runtime': 'node',
'X-Stainless-Runtime-Version': (globalThis as any).process.version ?? 'unknown',
};
}
const browserInfo = getBrowserInfo();
if (browserInfo) {
return {
'X-Stainless-Lang': 'js',
'X-Stainless-Package-Version': VERSION,
'X-Stainless-OS': 'Unknown',
'X-Stainless-Arch': 'unknown',
'X-Stainless-Runtime': `browser:${browserInfo.browser}`,
'X-Stainless-Runtime-Version': browserInfo.version,
};
}
// TODO add support for Cloudflare workers, etc.
return {
'X-Stainless-Lang': 'js',
'X-Stainless-Package-Version': VERSION,
'X-Stainless-OS': 'Unknown',
'X-Stainless-Arch': 'unknown',
'X-Stainless-Runtime': 'unknown',
'X-Stainless-Runtime-Version': 'unknown',
};
};
type BrowserInfo = {
browser: Browser;
version: string;
};
declare const navigator: { userAgent: string } | undefined;
// Note: modified from https://github.com/JS-DevTools/host-environment/blob/b1ab79ecde37db5d6e163c050e54fe7d287d7c92/src/isomorphic.browser.ts
function getBrowserInfo(): BrowserInfo | null {
if (typeof navigator === 'undefined' || !navigator) {
return null;
}
// NOTE: The order matters here!
const browserPatterns = [
{ key: 'edge' as const, pattern: /Edge(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
{ key: 'ie' as const, pattern: /MSIE(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
{ key: 'ie' as const, pattern: /Trident(?:.*rv\:(\d+)\.(\d+)(?:\.(\d+))?)?/ },
{ key: 'chrome' as const, pattern: /Chrome(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
{ key: 'firefox' as const, pattern: /Firefox(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
{ key: 'safari' as const, pattern: /(?:Version\W+(\d+)\.(\d+)(?:\.(\d+))?)?(?:\W+Mobile\S*)?\W+Safari/ },
];
// Find the FIRST matching browser
for (const { key, pattern } of browserPatterns) {
const match = pattern.exec(navigator.userAgent);
if (match) {
const major = match[1] || 0;
const minor = match[2] || 0;
const patch = match[3] || 0;
return { browser: key, version: `${major}.${minor}.${patch}` };
}
}
return null;
}
const normalizeArch = (arch: string): Arch => {
// Node docs:
// - https://nodejs.org/api/process.html#processarch
// Deno docs:
// - https://doc.deno.land/deno/stable/~/Deno.build
if (arch === 'x32') return 'x32';
if (arch === 'x86_64' || arch === 'x64') return 'x64';
if (arch === 'arm') return 'arm';
if (arch === 'aarch64' || arch === 'arm64') return 'arm64';
if (arch) return `other:${arch}`;
return 'unknown';
};
const normalizePlatform = (platform: string): PlatformName => {
// Node platforms:
// - https://nodejs.org/api/process.html#processplatform
// Deno platforms:
// - https://doc.deno.land/deno/stable/~/Deno.build
// - https://github.com/denoland/deno/issues/14799
platform = platform.toLowerCase();
// NOTE: this iOS check is untested and may not work
// Node does not work natively on IOS, there is a fork at
// https://github.com/nodejs-mobile/nodejs-mobile
// however it is unknown at the time of writing how to detect if it is running
if (platform.includes('ios')) return 'iOS';
if (platform === 'android') return 'Android';
if (platform === 'darwin') return 'MacOS';
if (platform === 'win32') return 'Windows';
if (platform === 'freebsd') return 'FreeBSD';
if (platform === 'openbsd') return 'OpenBSD';
if (platform === 'linux') return 'Linux';
if (platform) return `Other:${platform}`;
return 'Unknown';
};
let _platformHeaders: PlatformProperties;
export const getPlatformHeaders = () => {
return (_platformHeaders ??= getPlatformProperties());
};

View File

@@ -0,0 +1,33 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export function isAbortError(err: unknown) {
return (
typeof err === 'object' &&
err !== null &&
// Spec-compliant fetch implementations
(('name' in err && (err as any).name === 'AbortError') ||
// Expo fetch
('message' in err && String((err as any).message).includes('FetchRequestCanceledException')))
);
}
export const castToError = (err: any): Error => {
if (err instanceof Error) return err;
if (typeof err === 'object' && err !== null) {
try {
if (Object.prototype.toString.call(err) === '[object Error]') {
// @ts-ignore - not all envs have native support for cause yet
const error = new Error(err.message, err.cause ? { cause: err.cause } : {});
if (err.stack) error.stack = err.stack;
// @ts-ignore - not all envs have native support for cause yet
if (err.cause && !error.cause) error.cause = err.cause;
if (err.name) error.name = err.name;
return error;
}
} catch {}
try {
return new Error(JSON.stringify(err));
} catch {}
}
return new Error(err);
};

View File

@@ -0,0 +1,99 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { isReadonlyArray } from './utils/values';
type HeaderValue = string | undefined | null;
export type HeadersLike =
| Headers
| readonly HeaderValue[][]
| Record<string, HeaderValue | readonly HeaderValue[]>
| undefined
| null
| NullableHeaders;
const brand_privateNullableHeaders = Symbol.for('brand.privateNullableHeaders') as symbol & {
description: 'brand.privateNullableHeaders';
};
/**
* @internal
* Users can pass explicit nulls to unset default headers. When we parse them
* into a standard headers type we need to preserve that information.
*/
export type NullableHeaders = {
/** Brand check, prevent users from creating a NullableHeaders. */
[_: typeof brand_privateNullableHeaders]: true;
/** Parsed headers. */
values: Headers;
/** Set of lowercase header names explicitly set to null. */
nulls: Set<string>;
};
function* iterateHeaders(headers: HeadersLike): IterableIterator<readonly [string, string | null]> {
if (!headers) return;
if (brand_privateNullableHeaders in headers) {
const { values, nulls } = headers as NullableHeaders;
yield* values.entries();
for (const name of nulls) {
yield [name, null];
}
return;
}
let shouldClear = false;
let iter: Iterable<readonly (HeaderValue | readonly HeaderValue[])[]>;
if (headers instanceof Headers) {
iter = headers.entries();
} else if (isReadonlyArray(headers)) {
iter = headers;
} else {
shouldClear = true;
iter = Object.entries(headers ?? {});
}
for (let row of iter) {
const name = row[0];
if (typeof name !== 'string') throw new TypeError('expected header name to be a string');
const values = isReadonlyArray(row[1]) ? row[1] : [row[1]];
let didClear = false;
for (const value of values) {
if (value === undefined) continue;
// Objects keys always overwrite older headers, they never append.
// Yield a null to clear the header before adding the new values.
if (shouldClear && !didClear) {
didClear = true;
yield [name, null];
}
yield [name, value];
}
}
}
export const buildHeaders = (newHeaders: HeadersLike[]): NullableHeaders => {
const targetHeaders = new Headers();
const nullHeaders = new Set<string>();
for (const headers of newHeaders) {
const seenHeaders = new Set<string>();
for (const [name, value] of iterateHeaders(headers)) {
const lowerName = name.toLowerCase();
if (!seenHeaders.has(lowerName)) {
targetHeaders.delete(name);
seenHeaders.add(lowerName);
}
if (value === null) {
targetHeaders.delete(name);
nullHeaders.add(lowerName);
} else {
targetHeaders.append(name, value);
nullHeaders.delete(lowerName);
}
}
}
return { [brand_privateNullableHeaders]: true, values: targetHeaders, nulls: nullHeaders };
};
export const isEmptyHeaders = (headers: HeadersLike) => {
for (const _ of iterateHeaders(headers)) return false;
return true;
};

View File

@@ -0,0 +1,90 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import type { FinalRequestOptions } from './request-options';
import { Stream } from '../core/streaming';
import { type BaseAnthropic } from '../client';
import { formatRequestDetails, loggerFor } from './utils/log';
import type { AbstractPage } from '../core/pagination';
export type APIResponseProps = {
response: Response;
options: FinalRequestOptions;
controller: AbortController;
requestLogID: string;
retryOfRequestLogID: string | undefined;
startTime: number;
};
export async function defaultParseResponse<T>(
client: BaseAnthropic,
props: APIResponseProps,
): Promise<WithRequestID<T>> {
const { response, requestLogID, retryOfRequestLogID, startTime } = props;
const body = await (async () => {
if (props.options.stream) {
loggerFor(client).debug('response', response.status, response.url, response.headers, response.body);
// Note: there is an invariant here that isn't represented in the type system
// that if you set `stream: true` the response type must also be `Stream<T>`
if (props.options.__streamClass) {
return props.options.__streamClass.fromSSEResponse(response, props.controller) as any;
}
return Stream.fromSSEResponse(response, props.controller) as any;
}
// fetch refuses to read the body when the status code is 204.
if (response.status === 204) {
return null as T;
}
if (props.options.__binaryResponse) {
return response as unknown as T;
}
const contentType = response.headers.get('content-type');
const mediaType = contentType?.split(';')[0]?.trim();
const isJSON = mediaType?.includes('application/json') || mediaType?.endsWith('+json');
if (isJSON) {
const contentLength = response.headers.get('content-length');
if (contentLength === '0') {
// if there is no content we can't do anything
return undefined as T;
}
const json = await response.json();
return addRequestID(json as T, response);
}
const text = await response.text();
return text as unknown as T;
})();
loggerFor(client).debug(
`[${requestLogID}] response parsed`,
formatRequestDetails({
retryOfRequestLogID,
url: response.url,
status: response.status,
body,
durationMs: Date.now() - startTime,
}),
);
return body;
}
export type WithRequestID<T> =
T extends Array<any> | Response | AbstractPage<any> ? T
: T extends Record<string, any> ? T & { _request_id?: string | null }
: T;
export function addRequestID<T>(value: T, response: Response): WithRequestID<T> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return value as WithRequestID<T>;
}
return Object.defineProperty(value, '_request_id', {
value: response.headers.get('request-id'),
enumerable: false,
}) as WithRequestID<T>;
}

View File

@@ -0,0 +1,93 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { NullableHeaders } from './headers';
import type { BodyInit } from './builtin-types';
import { Stream } from '../core/streaming';
import type { HTTPMethod, MergedRequestInit } from './types';
import { type HeadersLike } from './headers';
export type FinalRequestOptions = RequestOptions & { method: HTTPMethod; path: string };
export type RequestOptions = {
/**
* The HTTP method for the request (e.g., 'get', 'post', 'put', 'delete').
*/
method?: HTTPMethod;
/**
* The URL path for the request.
*
* @example "/v1/foo"
*/
path?: string;
/**
* Query parameters to include in the request URL.
*/
query?: object | undefined | null;
/**
* The request body. Can be a string, JSON object, FormData, or other supported types.
*/
body?: unknown;
/**
* HTTP headers to include with the request. Can be a Headers object, plain object, or array of tuples.
*/
headers?: HeadersLike;
/**
* The maximum number of times that the client will retry a request in case of a
* temporary failure, like a network error or a 5XX error from the server.
*
* @default 2
*/
maxRetries?: number;
stream?: boolean | undefined;
/**
* The maximum amount of time (in milliseconds) that the client should wait for a response
* from the server before timing out a single request.
*
* @unit milliseconds
*/
timeout?: number;
/**
* Additional `RequestInit` options to be passed to the underlying `fetch` call.
* These options will be merged with the client's default fetch options.
*/
fetchOptions?: MergedRequestInit;
/**
* An AbortSignal that can be used to cancel the request.
*/
signal?: AbortSignal | undefined | null;
/**
* A unique key for this request to enable idempotency.
*/
idempotencyKey?: string;
/**
* Override the default base URL for this specific request.
*/
defaultBaseURL?: string | undefined;
__binaryResponse?: boolean | undefined;
__streamClass?: typeof Stream;
};
export type EncodedContent = { bodyHeaders: HeadersLike; body: BodyInit };
export type RequestEncoder = (request: { headers: NullableHeaders; body: unknown }) => EncodedContent;
export const FallbackEncoder: RequestEncoder = ({ headers, body }) => {
return {
bodyHeaders: {
'content-type': 'application/json',
},
body: JSON.stringify(body),
};
};

View File

@@ -0,0 +1,26 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
/**
* Shims for types that we can't always rely on being available globally.
*
* Note: these only exist at the type-level, there is no corresponding runtime
* version for any of these symbols.
*/
type NeverToAny<T> = T extends never ? any : T;
/** @ts-ignore */
type _DOMReadableStream<R = any> = globalThis.ReadableStream<R>;
/** @ts-ignore */
type _NodeReadableStream<R = any> = import('stream/web').ReadableStream<R>;
type _ConditionalNodeReadableStream<R = any> =
typeof globalThis extends { ReadableStream: any } ? never : _NodeReadableStream<R>;
type _ReadableStream<R = any> = NeverToAny<
| ([0] extends [1 & _DOMReadableStream<R>] ? never : _DOMReadableStream<R>)
| ([0] extends [1 & _ConditionalNodeReadableStream<R>] ? never : _ConditionalNodeReadableStream<R>)
>;
export type { _ReadableStream as ReadableStream };

View File

@@ -0,0 +1,107 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
/**
* This module provides internal shims and utility functions for environments where certain Node.js or global types may not be available.
*
* These are used to ensure we can provide a consistent behaviour between different JavaScript environments and good error
* messages in cases where an environment isn't fully supported.
*/
import type { Fetch } from './builtin-types';
import type { ReadableStream } from './shim-types';
export function getDefaultFetch(): Fetch {
if (typeof fetch !== 'undefined') {
return fetch as any;
}
throw new Error(
'`fetch` is not defined as a global; Either pass `fetch` to the client, `new Anthropic({ fetch })` or polyfill the global, `globalThis.fetch = fetch`',
);
}
type ReadableStreamArgs = ConstructorParameters<typeof ReadableStream>;
export function makeReadableStream(...args: ReadableStreamArgs): ReadableStream {
const ReadableStream = (globalThis as any).ReadableStream;
if (typeof ReadableStream === 'undefined') {
// Note: All of the platforms / runtimes we officially support already define
// `ReadableStream` as a global, so this should only ever be hit on unsupported runtimes.
throw new Error(
'`ReadableStream` is not defined as a global; You will need to polyfill it, `globalThis.ReadableStream = ReadableStream`',
);
}
return new ReadableStream(...args);
}
export function ReadableStreamFrom<T>(iterable: Iterable<T> | AsyncIterable<T>): ReadableStream<T> {
let iter: AsyncIterator<T> | Iterator<T> =
Symbol.asyncIterator in iterable ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator]();
return makeReadableStream({
start() {},
async pull(controller: any) {
const { done, value } = await iter.next();
if (done) {
controller.close();
} else {
controller.enqueue(value);
}
},
async cancel() {
await iter.return?.();
},
});
}
/**
* Most browsers don't yet have async iterable support for ReadableStream,
* and Node has a very different way of reading bytes from its "ReadableStream".
*
* This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490
*/
export function ReadableStreamToAsyncIterable<T>(stream: any): AsyncIterableIterator<T> {
if (stream[Symbol.asyncIterator]) return stream;
const reader = stream.getReader();
return {
async next() {
try {
const result = await reader.read();
if (result?.done) reader.releaseLock(); // release lock when stream becomes closed
return result;
} catch (e) {
reader.releaseLock(); // release lock when stream becomes errored
throw e;
}
},
async return() {
const cancelPromise = reader.cancel();
reader.releaseLock();
await cancelPromise;
return { done: true, value: undefined };
},
[Symbol.asyncIterator]() {
return this;
},
};
}
/**
* Cancels a ReadableStream we don't need to consume.
* See https://undici.nodejs.org/#/?id=garbage-collection
*/
export async function CancelReadableStream(stream: any): Promise<void> {
if (stream === null || typeof stream !== 'object') return;
if (stream[Symbol.asyncIterator]) {
await stream[Symbol.asyncIterator]().return?.();
return;
}
const reader = stream.getReader();
const cancelPromise = reader.cancel();
reader.releaseLock();
await cancelPromise;
}

View File

@@ -0,0 +1,32 @@
/**
* Most browsers don't yet have async iterable support for ReadableStream,
* and Node has a very different way of reading bytes from its "ReadableStream".
*
* This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490
*/
export function ReadableStreamToAsyncIterable<T>(stream: any): AsyncIterableIterator<T> {
if (stream[Symbol.asyncIterator]) return stream;
const reader = stream.getReader();
return {
async next() {
try {
const result = await reader.read();
if (result?.done) reader.releaseLock(); // release lock when stream becomes closed
return result;
} catch (e) {
reader.releaseLock(); // release lock when stream becomes errored
throw e;
}
},
async return() {
const cancelPromise = reader.cancel();
reader.releaseLock();
await cancelPromise;
return { done: true, value: undefined };
},
[Symbol.asyncIterator]() {
return this;
},
};
}

View File

@@ -0,0 +1,159 @@
import { BlobPart, getName, makeFile, isAsyncIterable } from './uploads';
import type { FilePropertyBag } from './builtin-types';
import { checkFileSupport } from './uploads';
type BlobLikePart = string | ArrayBuffer | ArrayBufferView | BlobLike | DataView;
/**
* Intended to match DOM Blob, node-fetch Blob, node:buffer Blob, etc.
* Don't add arrayBuffer here, node-fetch doesn't have it
*/
interface BlobLike {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */
readonly size: number;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */
readonly type: string;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */
text(): Promise<string>;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */
slice(start?: number, end?: number): BlobLike;
}
/**
* This check adds the arrayBuffer() method type because it is available and used at runtime
*/
const isBlobLike = (value: any): value is BlobLike & { arrayBuffer(): Promise<ArrayBuffer> } =>
value != null &&
typeof value === 'object' &&
typeof value.size === 'number' &&
typeof value.type === 'string' &&
typeof value.text === 'function' &&
typeof value.slice === 'function' &&
typeof value.arrayBuffer === 'function';
/**
* Intended to match DOM File, node:buffer File, undici File, etc.
*/
interface FileLike extends BlobLike {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */
readonly lastModified: number;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */
readonly name?: string | undefined;
}
/**
* This check adds the arrayBuffer() method type because it is available and used at runtime
*/
const isFileLike = (value: any): value is FileLike & { arrayBuffer(): Promise<ArrayBuffer> } =>
value != null &&
typeof value === 'object' &&
typeof value.name === 'string' &&
typeof value.lastModified === 'number' &&
isBlobLike(value);
/**
* Intended to match DOM Response, node-fetch Response, undici Response, etc.
*/
export interface ResponseLike {
url: string;
blob(): Promise<BlobLike>;
}
const isResponseLike = (value: any): value is ResponseLike =>
value != null &&
typeof value === 'object' &&
typeof value.url === 'string' &&
typeof value.blob === 'function';
export type ToFileInput =
| FileLike
| ResponseLike
| Exclude<BlobLikePart, string>
| AsyncIterable<BlobLikePart>;
/**
* Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats
* @param value the raw content of the file. Can be an {@link Uploadable}, BlobLikePart, or AsyncIterable of BlobLikeParts
* @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible
* @param {Object=} options additional properties
* @param {string=} options.type the MIME type of the content
* @param {number=} options.lastModified the last modified timestamp
* @returns a {@link File} with the given properties
*/
export async function toFile(
value: ToFileInput | PromiseLike<ToFileInput>,
name?: string | null | undefined,
options?: FilePropertyBag | undefined,
): Promise<File> {
checkFileSupport();
// If it's a promise, resolve it.
value = await value;
name ||= getName(value, true);
// If we've been given a `File` we don't need to do anything if the name / options
// have not been customised.
if (isFileLike(value)) {
if (value instanceof File && name == null && options == null) {
return value;
}
return makeFile([await value.arrayBuffer()], name ?? value.name, {
type: value.type,
lastModified: value.lastModified,
...options,
});
}
if (isResponseLike(value)) {
const blob = await value.blob();
name ||= new URL(value.url).pathname.split(/[\\/]/).pop();
return makeFile(await getBytes(blob), name, options);
}
const parts = await getBytes(value);
if (!options?.type) {
const type = parts.find((part) => typeof part === 'object' && 'type' in part && part.type);
if (typeof type === 'string') {
options = { ...options, type };
}
}
return makeFile(parts, name, options);
}
async function getBytes(value: BlobLikePart | AsyncIterable<BlobLikePart>): Promise<Array<BlobPart>> {
let parts: Array<BlobPart> = [];
if (
typeof value === 'string' ||
ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc.
value instanceof ArrayBuffer
) {
parts.push(value);
} else if (isBlobLike(value)) {
parts.push(value instanceof Blob ? value : await value.arrayBuffer());
} else if (
isAsyncIterable(value) // includes Readable, ReadableStream, etc.
) {
for await (const chunk of value) {
parts.push(...(await getBytes(chunk as BlobLikePart))); // TODO, consider validating?
}
} else {
const constructor = value?.constructor?.name;
throw new Error(
`Unexpected data type: ${typeof value}${
constructor ? `; constructor: ${constructor}` : ''
}${propsForError(value)}`,
);
}
return parts;
}
function propsForError(value: unknown): string {
if (typeof value !== 'object' || value === null) return '';
const props = Object.getOwnPropertyNames(value);
return `; props: [${props.map((p) => `"${p}"`).join(', ')}]`;
}

View File

@@ -0,0 +1,95 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export type PromiseOrValue<T> = T | Promise<T>;
export type HTTPMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';
export type KeysEnum<T> = { [P in keyof Required<T>]: true };
export type FinalizedRequestInit = RequestInit & { headers: Headers };
type NotAny<T> = [0] extends [1 & T] ? never : T;
/**
* Some environments overload the global fetch function, and Parameters<T> only gets the last signature.
*/
type OverloadedParameters<T> =
T extends (
{
(...args: infer A): unknown;
(...args: infer B): unknown;
(...args: infer C): unknown;
(...args: infer D): unknown;
}
) ?
A | B | C | D
: T extends (
{
(...args: infer A): unknown;
(...args: infer B): unknown;
(...args: infer C): unknown;
}
) ?
A | B | C
: T extends (
{
(...args: infer A): unknown;
(...args: infer B): unknown;
}
) ?
A | B
: T extends (...args: infer A) => unknown ? A
: never;
/* eslint-disable */
/**
* These imports attempt to get types from a parent package's dependencies.
* Unresolved bare specifiers can trigger [automatic type acquisition][1] in some projects, which
* would cause typescript to show types not present at runtime. To avoid this, we import
* directly from parent node_modules folders.
*
* We need to check multiple levels because we don't know what directory structure we'll be in.
* For example, pnpm generates directories like this:
* ```
* node_modules
* ├── .pnpm
* │ └── pkg@1.0.0
* │ └── node_modules
* │ └── pkg
* │ └── internal
* │ └── types.d.ts
* ├── pkg -> .pnpm/pkg@1.0.0/node_modules/pkg
* └── undici
* ```
*
* [1]: https://www.typescriptlang.org/tsconfig/#typeAcquisition
*/
/** @ts-ignore For users with \@types/node */
type UndiciTypesRequestInit = NotAny<import('undici-types').RequestInit> | NotAny<import('undici-types').RequestInit> | NotAny<import('../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../../node_modules/undici-types/index.d.ts').RequestInit>;
/** @ts-ignore For users with undici */
type UndiciRequestInit = NotAny<import('undici').RequestInit> | NotAny<import('undici').RequestInit> | NotAny<import('../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../../node_modules/undici/index.d.ts').RequestInit>;
/** @ts-ignore For users with \@types/bun */
type BunRequestInit = globalThis.FetchRequestInit;
/** @ts-ignore For users with node-fetch@2 */
type NodeFetch2RequestInit = NotAny<import('node-fetch').RequestInit> | NotAny<import('node-fetch').RequestInit> | NotAny<import('../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit>;
/** @ts-ignore For users with node-fetch@3, doesn't need file extension because types are at ./@types/index.d.ts */
type NodeFetch3RequestInit = NotAny<import('node-fetch.js').RequestInit> | NotAny<import('node-fetch.js').RequestInit> | NotAny<import('../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../../../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../../../../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../../../../../../../node_modules/node-fetch').RequestInit>;
/** @ts-ignore For users who use Deno */
type FetchRequestInit = NonNullable<OverloadedParameters<typeof fetch>[1]>;
/* eslint-enable */
type RequestInits =
| NotAny<UndiciTypesRequestInit>
| NotAny<UndiciRequestInit>
| NotAny<BunRequestInit>
| NotAny<NodeFetch2RequestInit>
| NotAny<NodeFetch3RequestInit>
| NotAny<RequestInit>
| NotAny<FetchRequestInit>;
/**
* This type contains `RequestInit` options that may be available on the current runtime,
* including per-platform extensions like `dispatcher`, `agent`, `client`, etc.
*/
export type MergedRequestInit = RequestInits &
/** We don't include these in the types as they'll be overridden for every request. */
Partial<Record<'body' | 'headers' | 'method' | 'signal', never>>;

View File

@@ -0,0 +1,204 @@
import { type RequestOptions } from './request-options';
import type { FilePropertyBag, Fetch } from './builtin-types';
import type { BaseAnthropic } from '../client';
import { ReadableStreamFrom } from './shims';
export type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob | DataView;
type FsReadStream = AsyncIterable<Uint8Array> & { path: string | { toString(): string } };
// https://github.com/oven-sh/bun/issues/5980
interface BunFile extends Blob {
readonly name?: string | undefined;
}
export const checkFileSupport = () => {
if (typeof File === 'undefined') {
const { process } = globalThis as any;
const isOldNode =
typeof process?.versions?.node === 'string' && parseInt(process.versions.node.split('.')) < 20;
throw new Error(
'`File` is not defined as a global, which is required for file uploads.' +
(isOldNode ?
" Update to Node 20 LTS or newer, or set `globalThis.File` to `import('node:buffer').File`."
: ''),
);
}
};
/**
* Typically, this is a native "File" class.
*
* We provide the {@link toFile} utility to convert a variety of objects
* into the File class.
*
* For convenience, you can also pass a fetch Response, or in Node,
* the result of fs.createReadStream().
*/
export type Uploadable = File | Response | FsReadStream | BunFile;
/**
* Construct a `File` instance. This is used to ensure a helpful error is thrown
* for environments that don't define a global `File` yet.
*/
export function makeFile(
fileBits: BlobPart[],
fileName: string | undefined,
options?: FilePropertyBag,
): File {
checkFileSupport();
return new File(fileBits as any, fileName ?? 'unknown_file', options);
}
export function getName(value: any, stripPath: boolean): string | undefined {
const val =
(typeof value === 'object' &&
value !== null &&
(('name' in value && value.name && String(value.name)) ||
('url' in value && value.url && String(value.url)) ||
('filename' in value && value.filename && String(value.filename)) ||
('path' in value && value.path && String(value.path)))) ||
'';
return stripPath ? val.split(/[\\/]/).pop() || undefined : val;
}
export const isAsyncIterable = (value: any): value is AsyncIterable<any> =>
value != null && typeof value === 'object' && typeof value[Symbol.asyncIterator] === 'function';
/**
* Returns a multipart/form-data request if any part of the given request body contains a File / Blob value.
* Otherwise returns the request as is.
*/
export const maybeMultipartFormRequestOptions = async (
opts: RequestOptions,
fetch: BaseAnthropic | Fetch,
): Promise<RequestOptions> => {
if (!hasUploadableValue(opts.body)) return opts;
return { ...opts, body: await createForm(opts.body, fetch) };
};
type MultipartFormRequestOptions = Omit<RequestOptions, 'body'> & { body: unknown };
export const multipartFormRequestOptions = async (
opts: MultipartFormRequestOptions,
fetch: BaseAnthropic | Fetch,
stripFilenames: boolean = true,
): Promise<RequestOptions> => {
return { ...opts, body: await createForm(opts.body, fetch, stripFilenames) };
};
const supportsFormDataMap = /* @__PURE__ */ new WeakMap<Fetch, Promise<boolean>>();
/**
* node-fetch doesn't support the global FormData object in recent node versions. Instead of sending
* properly-encoded form data, it just stringifies the object, resulting in a request body of "[object FormData]".
* This function detects if the fetch function provided supports the global FormData object to avoid
* confusing error messages later on.
*/
function supportsFormData(fetchObject: BaseAnthropic | Fetch): Promise<boolean> {
const fetch: Fetch = typeof fetchObject === 'function' ? fetchObject : (fetchObject as any).fetch;
const cached = supportsFormDataMap.get(fetch);
if (cached) return cached;
const promise = (async () => {
try {
const FetchResponse = (
'Response' in fetch ?
fetch.Response
: (await fetch('data:,')).constructor) as typeof Response;
const data = new FormData();
if (data.toString() === (await new FetchResponse(data).text())) {
return false;
}
return true;
} catch {
// avoid false negatives
return true;
}
})();
supportsFormDataMap.set(fetch, promise);
return promise;
}
export const createForm = async <T = Record<string, unknown>>(
body: T | undefined,
fetch: BaseAnthropic | Fetch,
stripFilenames: boolean = true,
): Promise<FormData> => {
if (!(await supportsFormData(fetch))) {
throw new TypeError(
'The provided fetch function does not support file uploads with the current global FormData class.',
);
}
const form = new FormData();
await Promise.all(
Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value, stripFilenames)),
);
return form;
};
// We check for Blob not File because Bun.File doesn't inherit from File,
// but they both inherit from Blob and have a `name` property at runtime.
const isNamedBlob = (value: unknown): value is Blob => value instanceof Blob && 'name' in value;
const isUploadable = (value: unknown) =>
typeof value === 'object' &&
value !== null &&
(value instanceof Response || isAsyncIterable(value) || isNamedBlob(value));
const hasUploadableValue = (value: unknown): boolean => {
if (isUploadable(value)) return true;
if (Array.isArray(value)) return value.some(hasUploadableValue);
if (value && typeof value === 'object') {
for (const k in value) {
if (hasUploadableValue((value as any)[k])) return true;
}
}
return false;
};
const addFormValue = async (
form: FormData,
key: string,
value: unknown,
stripFilenames: boolean,
): Promise<void> => {
if (value === undefined) return;
if (value == null) {
throw new TypeError(
`Received null for "${key}"; to pass null in FormData, you must use the string 'null'`,
);
}
// TODO: make nested formats configurable
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
form.append(key, String(value));
} else if (value instanceof Response) {
let options = {} as FilePropertyBag;
const contentType = value.headers.get('Content-Type');
if (contentType) {
options = { type: contentType };
}
form.append(key, makeFile([await value.blob()], getName(value, stripFilenames), options));
} else if (isAsyncIterable(value)) {
form.append(
key,
makeFile([await new Response(ReadableStreamFrom(value)).blob()], getName(value, stripFilenames)),
);
} else if (isNamedBlob(value)) {
form.append(key, makeFile([value], getName(value, stripFilenames), { type: value.type }));
} else if (Array.isArray(value)) {
await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry, stripFilenames)));
} else if (typeof value === 'object') {
await Promise.all(
Object.entries(value).map(([name, prop]) =>
addFormValue(form, `${key}[${name}]`, prop, stripFilenames),
),
);
} else {
throw new TypeError(
`Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`,
);
}
};

View File

@@ -0,0 +1,9 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export * from './utils/values';
export * from './utils/base64';
export * from './utils/env';
export * from './utils/log';
export * from './utils/uuid';
export * from './utils/sleep';
export * from './utils/query';

View File

@@ -0,0 +1,40 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { AnthropicError } from '../../core/error';
import { encodeUTF8 } from './bytes';
export const toBase64 = (data: string | Uint8Array | null | undefined): string => {
if (!data) return '';
if (typeof (globalThis as any).Buffer !== 'undefined') {
return (globalThis as any).Buffer.from(data).toString('base64');
}
if (typeof data === 'string') {
data = encodeUTF8(data);
}
if (typeof btoa !== 'undefined') {
return btoa(String.fromCharCode.apply(null, data as any));
}
throw new AnthropicError('Cannot generate base64 string; Expected `Buffer` or `btoa` to be defined');
};
export const fromBase64 = (str: string): Uint8Array => {
if (typeof (globalThis as any).Buffer !== 'undefined') {
const buf = (globalThis as any).Buffer.from(str, 'base64');
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
}
if (typeof atob !== 'undefined') {
const bstr = atob(str);
const buf = new Uint8Array(bstr.length);
for (let i = 0; i < bstr.length; i++) {
buf[i] = bstr.charCodeAt(i);
}
return buf;
}
throw new AnthropicError('Cannot decode base64 string; Expected `Buffer` or `atob` to be defined');
};

View File

@@ -0,0 +1,32 @@
export function concatBytes(buffers: Uint8Array[]): Uint8Array {
let length = 0;
for (const buffer of buffers) {
length += buffer.length;
}
const output = new Uint8Array(length);
let index = 0;
for (const buffer of buffers) {
output.set(buffer, index);
index += buffer.length;
}
return output;
}
let encodeUTF8_: (str: string) => Uint8Array;
export function encodeUTF8(str: string) {
let encoder;
return (
encodeUTF8_ ??
((encoder = new (globalThis as any).TextEncoder()), (encodeUTF8_ = encoder.encode.bind(encoder)))
)(str);
}
let decodeUTF8_: (bytes: Uint8Array) => string;
export function decodeUTF8(bytes: Uint8Array) {
let decoder;
return (
decodeUTF8_ ??
((decoder = new (globalThis as any).TextDecoder()), (decodeUTF8_ = decoder.decode.bind(decoder)))
)(bytes);
}

View File

@@ -0,0 +1,18 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
/**
* Read an environment variable.
*
* Trims beginning and trailing whitespace.
*
* Will return undefined if the environment variable doesn't exist or cannot be accessed.
*/
export const readEnv = (env: string): string | undefined => {
if (typeof (globalThis as any).process !== 'undefined') {
return (globalThis as any).process.env?.[env]?.trim() ?? undefined;
}
if (typeof (globalThis as any).Deno !== 'undefined') {
return (globalThis as any).Deno.env?.get?.(env)?.trim();
}
return undefined;
};

View File

@@ -0,0 +1,127 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { hasOwn } from './values';
import { type BaseAnthropic } from '../../client';
import { RequestOptions } from '../request-options';
type LogFn = (message: string, ...rest: unknown[]) => void;
export type Logger = {
error: LogFn;
warn: LogFn;
info: LogFn;
debug: LogFn;
};
export type LogLevel = 'off' | 'error' | 'warn' | 'info' | 'debug';
const levelNumbers = {
off: 0,
error: 200,
warn: 300,
info: 400,
debug: 500,
};
export const parseLogLevel = (
maybeLevel: string | undefined,
sourceName: string,
client: BaseAnthropic,
): LogLevel | undefined => {
if (!maybeLevel) {
return undefined;
}
if (hasOwn(levelNumbers, maybeLevel)) {
return maybeLevel;
}
loggerFor(client).warn(
`${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify(
Object.keys(levelNumbers),
)}`,
);
return undefined;
};
function noop() {}
function makeLogFn(fnLevel: keyof Logger, logger: Logger | undefined, logLevel: LogLevel) {
if (!logger || levelNumbers[fnLevel] > levelNumbers[logLevel]) {
return noop;
} else {
// Don't wrap logger functions, we want the stacktrace intact!
return logger[fnLevel].bind(logger);
}
}
const noopLogger = {
error: noop,
warn: noop,
info: noop,
debug: noop,
};
let cachedLoggers = /* @__PURE__ */ new WeakMap<Logger, [LogLevel, Logger]>();
export function loggerFor(client: BaseAnthropic): Logger {
const logger = client.logger;
const logLevel = client.logLevel ?? 'off';
if (!logger) {
return noopLogger;
}
const cachedLogger = cachedLoggers.get(logger);
if (cachedLogger && cachedLogger[0] === logLevel) {
return cachedLogger[1];
}
const levelLogger = {
error: makeLogFn('error', logger, logLevel),
warn: makeLogFn('warn', logger, logLevel),
info: makeLogFn('info', logger, logLevel),
debug: makeLogFn('debug', logger, logLevel),
};
cachedLoggers.set(logger, [logLevel, levelLogger]);
return levelLogger;
}
export const formatRequestDetails = (details: {
options?: RequestOptions | undefined;
headers?: Headers | Record<string, string> | undefined;
retryOfRequestLogID?: string | undefined;
retryOf?: string | undefined;
url?: string | undefined;
status?: number | undefined;
method?: string | undefined;
durationMs?: number | undefined;
message?: unknown;
body?: unknown;
}) => {
if (details.options) {
details.options = { ...details.options };
delete details.options['headers']; // redundant + leaks internals
}
if (details.headers) {
details.headers = Object.fromEntries(
(details.headers instanceof Headers ? [...details.headers] : Object.entries(details.headers)).map(
([name, value]) => [
name,
(
name.toLowerCase() === 'x-api-key' ||
name.toLowerCase() === 'authorization' ||
name.toLowerCase() === 'cookie' ||
name.toLowerCase() === 'set-cookie'
) ?
'***'
: value,
],
),
);
}
if ('retryOfRequestLogID' in details) {
if (details.retryOfRequestLogID) {
details.retryOf = details.retryOfRequestLogID;
}
delete details.retryOfRequestLogID;
}
return details;
};

View File

@@ -0,0 +1,88 @@
import { AnthropicError } from '../../core/error';
/**
* Percent-encode everything that isn't safe to have in a path without encoding safe chars.
*
* Taken from https://datatracker.ietf.org/doc/html/rfc3986#section-3.3:
* > unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* > sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
* > pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
*/
export function encodeURIPath(str: string) {
return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
}
const EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
export const createPathTagFunction = (pathEncoder = encodeURIPath) =>
function path(statics: readonly string[], ...params: readonly unknown[]): string {
// If there are no params, no processing is needed.
if (statics.length === 1) return statics[0]!;
let postPath = false;
const invalidSegments = [];
const path = statics.reduce((previousValue, currentValue, index) => {
if (/[?#]/.test(currentValue)) {
postPath = true;
}
const value = params[index];
let encoded = (postPath ? encodeURIComponent : pathEncoder)('' + value);
if (
index !== params.length &&
(value == null ||
(typeof value === 'object' &&
// handle values from other realms
value.toString ===
Object.getPrototypeOf(Object.getPrototypeOf((value as any).hasOwnProperty ?? EMPTY) ?? EMPTY)
?.toString))
) {
encoded = value + '';
invalidSegments.push({
start: previousValue.length + currentValue.length,
length: encoded.length,
error: `Value of type ${Object.prototype.toString
.call(value)
.slice(8, -1)} is not a valid path parameter`,
});
}
return previousValue + currentValue + (index === params.length ? '' : encoded);
}, '');
const pathOnly = path.split(/[?#]/, 1)[0]!;
const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
let match;
// Find all invalid segments
while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
invalidSegments.push({
start: match.index,
length: match[0].length,
error: `Value "${match[0]}" can\'t be safely passed as a path parameter`,
});
}
invalidSegments.sort((a, b) => a.start - b.start);
if (invalidSegments.length > 0) {
let lastEnd = 0;
const underline = invalidSegments.reduce((acc, segment) => {
const spaces = ' '.repeat(segment.start - lastEnd);
const arrows = '^'.repeat(segment.length);
lastEnd = segment.start + segment.length;
return acc + spaces + arrows;
}, '');
throw new AnthropicError(
`Path parameters result in path with invalid segments:\n${invalidSegments
.map((e) => e.error)
.join('\n')}\n${path}\n${underline}`,
);
}
return path;
};
/**
* URI-encodes path params and ensures no unsafe /./ or /../ path segments are introduced.
*/
export const path = /* @__PURE__ */ createPathTagFunction(encodeURIPath);

View File

@@ -0,0 +1,23 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { AnthropicError } from '../../core/error';
/**
* Basic re-implementation of `qs.stringify` for primitive types.
*/
export function stringifyQuery(query: object | Record<string, unknown>) {
return Object.entries(query)
.filter(([_, value]) => typeof value !== 'undefined')
.map(([key, value]) => {
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
}
if (value === null) {
return `${encodeURIComponent(key)}=`;
}
throw new AnthropicError(
`Cannot stringify type ${typeof value}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.`,
);
})
.join('&');
}

View File

@@ -0,0 +1,3 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));

View File

@@ -0,0 +1,17 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
/**
* https://stackoverflow.com/a/2117523
*/
export let uuid4 = function () {
const { crypto } = globalThis as any;
if (crypto?.randomUUID) {
uuid4 = crypto.randomUUID.bind(crypto);
return crypto.randomUUID();
}
const u8 = new Uint8Array(1);
const randomByte = crypto ? () => crypto.getRandomValues(u8)[0]! : () => (Math.random() * 0xff) & 0xff;
return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) =>
(+c ^ (randomByte() & (15 >> (+c / 4)))).toString(16),
);
};

View File

@@ -0,0 +1,112 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { AnthropicError } from '../../core/error';
// https://url.spec.whatwg.org/#url-scheme-string
const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i;
export const isAbsoluteURL = (url: string): boolean => {
return startsWithSchemeRegexp.test(url);
};
export let isArray = (val: unknown): val is unknown[] => ((isArray = Array.isArray), isArray(val));
export let isReadonlyArray = isArray as (val: unknown) => val is readonly unknown[];
/** Returns an object if the given value isn't an object, otherwise returns as-is */
export function maybeObj(x: unknown): object {
if (typeof x !== 'object') {
return {};
}
return x ?? {};
}
// https://stackoverflow.com/a/34491287
export function isEmptyObj(obj: Object | null | undefined): boolean {
if (!obj) return true;
for (const _k in obj) return false;
return true;
}
// https://eslint.org/docs/latest/rules/no-prototype-builtins
export function hasOwn<T extends object = object>(obj: T, key: PropertyKey): key is keyof T {
return Object.prototype.hasOwnProperty.call(obj, key);
}
export function isObj(obj: unknown): obj is Record<string, unknown> {
return obj != null && typeof obj === 'object' && !Array.isArray(obj);
}
export const ensurePresent = <T>(value: T | null | undefined): T => {
if (value == null) {
throw new AnthropicError(`Expected a value to be given but received ${value} instead.`);
}
return value;
};
export const validatePositiveInteger = (name: string, n: unknown): number => {
if (typeof n !== 'number' || !Number.isInteger(n)) {
throw new AnthropicError(`${name} must be an integer`);
}
if (n < 0) {
throw new AnthropicError(`${name} must be a positive integer`);
}
return n;
};
export const coerceInteger = (value: unknown): number => {
if (typeof value === 'number') return Math.round(value);
if (typeof value === 'string') return parseInt(value, 10);
throw new AnthropicError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
};
export const coerceFloat = (value: unknown): number => {
if (typeof value === 'number') return value;
if (typeof value === 'string') return parseFloat(value);
throw new AnthropicError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
};
export const coerceBoolean = (value: unknown): boolean => {
if (typeof value === 'boolean') return value;
if (typeof value === 'string') return value === 'true';
return Boolean(value);
};
export const maybeCoerceInteger = (value: unknown): number | undefined => {
if (value == null) {
return undefined;
}
return coerceInteger(value);
};
export const maybeCoerceFloat = (value: unknown): number | undefined => {
if (value == null) {
return undefined;
}
return coerceFloat(value);
};
export const maybeCoerceBoolean = (value: unknown): boolean | undefined => {
if (value == null) {
return undefined;
}
return coerceBoolean(value);
};
export const safeJSON = (text: string) => {
try {
return JSON.parse(text);
} catch (err) {
return undefined;
}
};
// Gets a value from an object, deletes the key, and returns the value (or undefined if not found)
export const pop = <T extends Record<string, any>, K extends string>(obj: T, key: K): T[K] => {
const value = obj[key];
delete obj[key];
return value;
};

View File

@@ -0,0 +1,4 @@
File generated from our OpenAPI spec by Stainless.
This directory can be used to store custom files to expand the SDK.
It is ignored by Stainless code generation and its content (other than this keep file) won't be touched.

View File

@@ -0,0 +1,764 @@
import { partialParse } from '../_vendor/partial-json-parser/parser';
import type { Logger } from '../client';
import { AnthropicError, APIUserAbortError } from '../error';
import { isAbortError } from '../internal/errors';
import { type RequestOptions } from '../internal/request-options';
import {
type BetaContentBlock,
type BetaMCPToolUseBlock,
type BetaMessage,
type BetaMessageParam,
Messages as BetaMessages,
type BetaRawMessageStreamEvent as BetaMessageStreamEvent,
type BetaServerToolUseBlock,
type BetaTextBlock,
type BetaTextCitation,
type BetaToolUseBlock,
type MessageCreateParams,
type MessageCreateParamsBase,
MessageCreateParamsStreaming,
} from '../resources/beta/messages/messages';
import { Stream } from '../streaming';
import { maybeParseBetaMessage, type ParsedBetaMessage } from './beta-parser';
export interface MessageStreamEvents {
connect: () => void;
streamEvent: (event: BetaMessageStreamEvent, snapshot: BetaMessage) => void;
text: (textDelta: string, textSnapshot: string) => void;
citation: (citation: BetaTextCitation, citationsSnapshot: BetaTextCitation[]) => void;
inputJson: (partialJson: string, jsonSnapshot: unknown) => void;
thinking: (thinkingDelta: string, thinkingSnapshot: string) => void;
signature: (signature: string) => void;
compaction: (compactedContent: string) => void;
message: (message: BetaMessage) => void;
contentBlock: (content: BetaContentBlock) => void;
finalMessage: (message: BetaMessage) => void;
error: (error: AnthropicError) => void;
abort: (error: APIUserAbortError) => void;
end: () => void;
}
type MessageStreamEventListeners<Event extends keyof MessageStreamEvents> = {
listener: MessageStreamEvents[Event];
once?: boolean;
}[];
const JSON_BUF_PROPERTY = '__json_buf';
export type TracksToolInput = BetaToolUseBlock | BetaServerToolUseBlock | BetaMCPToolUseBlock;
function tracksToolInput(content: BetaContentBlock): content is TracksToolInput {
return content.type === 'tool_use' || content.type === 'server_tool_use' || content.type === 'mcp_tool_use';
}
export class BetaMessageStream<ParsedT = null> implements AsyncIterable<BetaMessageStreamEvent> {
messages: BetaMessageParam[] = [];
receivedMessages: ParsedBetaMessage<ParsedT>[] = [];
#currentMessageSnapshot: BetaMessage | undefined;
#params: MessageCreateParams | null = null;
controller: AbortController = new AbortController();
#connectedPromise: Promise<Response | null>;
#resolveConnectedPromise: (response: Response | null) => void = () => {};
#rejectConnectedPromise: (error: AnthropicError) => void = () => {};
#endPromise: Promise<void>;
#resolveEndPromise: () => void = () => {};
#rejectEndPromise: (error: AnthropicError) => void = () => {};
#listeners: { [Event in keyof MessageStreamEvents]?: MessageStreamEventListeners<Event> } = {};
#ended = false;
#errored = false;
#aborted = false;
#catchingPromiseCreated = false;
#response: Response | null | undefined;
#request_id: string | null | undefined;
#logger: Logger;
constructor(params: MessageCreateParamsBase | null, opts?: { logger?: Logger | undefined }) {
this.#connectedPromise = new Promise<Response | null>((resolve, reject) => {
this.#resolveConnectedPromise = resolve;
this.#rejectConnectedPromise = reject;
});
this.#endPromise = new Promise<void>((resolve, reject) => {
this.#resolveEndPromise = resolve;
this.#rejectEndPromise = reject;
});
// Don't let these promises cause unhandled rejection errors.
// we will manually cause an unhandled rejection error later
// if the user hasn't registered any error listener or called
// any promise-returning method.
this.#connectedPromise.catch(() => {});
this.#endPromise.catch(() => {});
this.#params = params;
this.#logger = opts?.logger ?? console;
}
get response(): Response | null | undefined {
return this.#response;
}
get request_id(): string | null | undefined {
return this.#request_id;
}
/**
* Returns the `MessageStream` data, the raw `Response` instance and the ID of the request,
* returned vie the `request-id` header which is useful for debugging requests and resporting
* issues to Anthropic.
*
* This is the same as the `APIPromise.withResponse()` method.
*
* This method will raise an error if you created the stream using `MessageStream.fromReadableStream`
* as no `Response` is available.
*/
async withResponse(): Promise<{
data: BetaMessageStream<ParsedT>;
response: Response;
request_id: string | null | undefined;
}> {
this.#catchingPromiseCreated = true;
const response = await this.#connectedPromise;
if (!response) {
throw new Error('Could not resolve a `Response` object');
}
return {
data: this,
response,
request_id: response.headers.get('request-id'),
};
}
/**
* Intended for use on the frontend, consuming a stream produced with
* `.toReadableStream()` on the backend.
*
* Note that messages sent to the model do not appear in `.on('message')`
* in this context.
*/
static fromReadableStream(stream: ReadableStream): BetaMessageStream {
const runner = new BetaMessageStream(null);
runner._run(() => runner._fromReadableStream(stream));
return runner;
}
static createMessage<ParsedT>(
messages: BetaMessages,
params: MessageCreateParamsBase,
options?: RequestOptions,
{ logger }: { logger?: Logger | undefined } = {},
): BetaMessageStream<ParsedT> {
const runner = new BetaMessageStream<ParsedT>(params as MessageCreateParamsStreaming, { logger });
for (const message of params.messages) {
runner._addMessageParam(message);
}
runner.#params = { ...params, stream: true };
runner._run(() =>
runner._createMessage(
messages,
{ ...params, stream: true },
{ ...options, headers: { ...options?.headers, 'X-Stainless-Helper-Method': 'stream' } },
),
);
return runner;
}
protected _run(executor: () => Promise<any>) {
executor().then(() => {
this._emitFinal();
this._emit('end');
}, this.#handleError);
}
protected _addMessageParam(message: BetaMessageParam) {
this.messages.push(message);
}
protected _addMessage(message: ParsedBetaMessage<ParsedT>, emit = true) {
this.receivedMessages.push(message);
if (emit) {
this._emit('message', message);
}
}
protected async _createMessage(
messages: BetaMessages,
params: MessageCreateParams,
options?: RequestOptions,
): Promise<void> {
const signal = options?.signal;
let abortHandler: (() => void) | undefined;
if (signal) {
if (signal.aborted) this.controller.abort();
abortHandler = this.controller.abort.bind(this.controller);
signal.addEventListener('abort', abortHandler);
}
try {
this.#beginRequest();
const { response, data: stream } = await messages
.create({ ...params, stream: true }, { ...options, signal: this.controller.signal })
.withResponse();
this._connected(response);
for await (const event of stream) {
this.#addStreamEvent(event);
}
if (stream.controller.signal?.aborted) {
throw new APIUserAbortError();
}
this.#endRequest();
} finally {
if (signal && abortHandler) {
signal.removeEventListener('abort', abortHandler);
}
}
}
protected _connected(response: Response | null) {
if (this.ended) return;
this.#response = response;
this.#request_id = response?.headers.get('request-id');
this.#resolveConnectedPromise(response);
this._emit('connect');
}
get ended(): boolean {
return this.#ended;
}
get errored(): boolean {
return this.#errored;
}
get aborted(): boolean {
return this.#aborted;
}
abort() {
this.controller.abort();
}
/**
* Adds the listener function to the end of the listeners array for the event.
* No checks are made to see if the listener has already been added. Multiple calls passing
* the same combination of event and listener will result in the listener being added, and
* called, multiple times.
* @returns this MessageStream, so that calls can be chained
*/
on<Event extends keyof MessageStreamEvents>(event: Event, listener: MessageStreamEvents[Event]): this {
const listeners: MessageStreamEventListeners<Event> =
this.#listeners[event] || (this.#listeners[event] = []);
listeners.push({ listener });
return this;
}
/**
* Removes the specified listener from the listener array for the event.
* off() will remove, at most, one instance of a listener from the listener array. If any single
* listener has been added multiple times to the listener array for the specified event, then
* off() must be called multiple times to remove each instance.
* @returns this MessageStream, so that calls can be chained
*/
off<Event extends keyof MessageStreamEvents>(event: Event, listener: MessageStreamEvents[Event]): this {
const listeners = this.#listeners[event];
if (!listeners) return this;
const index = listeners.findIndex((l) => l.listener === listener);
if (index >= 0) listeners.splice(index, 1);
return this;
}
/**
* Adds a one-time listener function for the event. The next time the event is triggered,
* this listener is removed and then invoked.
* @returns this MessageStream, so that calls can be chained
*/
once<Event extends keyof MessageStreamEvents>(event: Event, listener: MessageStreamEvents[Event]): this {
const listeners: MessageStreamEventListeners<Event> =
this.#listeners[event] || (this.#listeners[event] = []);
listeners.push({ listener, once: true });
return this;
}
/**
* This is similar to `.once()`, but returns a Promise that resolves the next time
* the event is triggered, instead of calling a listener callback.
* @returns a Promise that resolves the next time given event is triggered,
* or rejects if an error is emitted. (If you request the 'error' event,
* returns a promise that resolves with the error).
*
* Example:
*
* const message = await stream.emitted('message') // rejects if the stream errors
*/
emitted<Event extends keyof MessageStreamEvents>(
event: Event,
): Promise<
Parameters<MessageStreamEvents[Event]> extends [infer Param] ? Param
: Parameters<MessageStreamEvents[Event]> extends [] ? void
: Parameters<MessageStreamEvents[Event]>
> {
return new Promise((resolve, reject) => {
this.#catchingPromiseCreated = true;
if (event !== 'error') this.once('error', reject);
this.once(event, resolve as any);
});
}
async done(): Promise<void> {
this.#catchingPromiseCreated = true;
await this.#endPromise;
}
get currentMessage(): BetaMessage | undefined {
return this.#currentMessageSnapshot;
}
#getFinalMessage(): ParsedBetaMessage<ParsedT> {
if (this.receivedMessages.length === 0) {
throw new AnthropicError('stream ended without producing a Message with role=assistant');
}
return this.receivedMessages.at(-1)!;
}
/**
* @returns a promise that resolves with the the final assistant Message response,
* or rejects if an error occurred or the stream ended prematurely without producing a Message.
* If structured outputs were used, this will be a ParsedMessage with a `parsed` field.
*/
async finalMessage(): Promise<ParsedBetaMessage<ParsedT>> {
await this.done();
return this.#getFinalMessage();
}
#getFinalText(): string {
if (this.receivedMessages.length === 0) {
throw new AnthropicError('stream ended without producing a Message with role=assistant');
}
const textBlocks = this.receivedMessages
.at(-1)!
.content.filter((block): block is BetaTextBlock => block.type === 'text')
.map((block) => block.text);
if (textBlocks.length === 0) {
throw new AnthropicError('stream ended without producing a content block with type=text');
}
return textBlocks.join(' ');
}
/**
* @returns a promise that resolves with the the final assistant Message's text response, concatenated
* together if there are more than one text blocks.
* Rejects if an error occurred or the stream ended prematurely without producing a Message.
*/
async finalText(): Promise<string> {
await this.done();
return this.#getFinalText();
}
#handleError = (error: unknown) => {
this.#errored = true;
if (isAbortError(error)) {
error = new APIUserAbortError();
}
if (error instanceof APIUserAbortError) {
this.#aborted = true;
return this._emit('abort', error);
}
if (error instanceof AnthropicError) {
return this._emit('error', error);
}
if (error instanceof Error) {
const anthropicError: AnthropicError = new AnthropicError(error.message);
// @ts-ignore
anthropicError.cause = error;
return this._emit('error', anthropicError);
}
return this._emit('error', new AnthropicError(String(error)));
};
protected _emit<Event extends keyof MessageStreamEvents>(
event: Event,
...args: Parameters<MessageStreamEvents[Event]>
) {
// make sure we don't emit any MessageStreamEvents after end
if (this.#ended) return;
if (event === 'end') {
this.#ended = true;
this.#resolveEndPromise();
}
const listeners: MessageStreamEventListeners<Event> | undefined = this.#listeners[event];
if (listeners) {
this.#listeners[event] = listeners.filter((l) => !l.once) as any;
listeners.forEach(({ listener }: any) => listener(...args));
}
if (event === 'abort') {
const error = args[0] as APIUserAbortError;
if (!this.#catchingPromiseCreated && !listeners?.length) {
Promise.reject(error);
}
this.#rejectConnectedPromise(error);
this.#rejectEndPromise(error);
this._emit('end');
return;
}
if (event === 'error') {
// NOTE: _emit('error', error) should only be called from #handleError().
const error = args[0] as AnthropicError;
if (!this.#catchingPromiseCreated && !listeners?.length) {
// Trigger an unhandled rejection if the user hasn't registered any error handlers.
// If you are seeing stack traces here, make sure to handle errors via either:
// - runner.on('error', () => ...)
// - await runner.done()
// - await runner.final...()
// - etc.
Promise.reject(error);
}
this.#rejectConnectedPromise(error);
this.#rejectEndPromise(error);
this._emit('end');
}
}
protected _emitFinal() {
const finalMessage = this.receivedMessages.at(-1);
if (finalMessage) {
this._emit('finalMessage', this.#getFinalMessage());
}
}
#beginRequest() {
if (this.ended) return;
this.#currentMessageSnapshot = undefined;
}
#addStreamEvent(event: BetaMessageStreamEvent) {
if (this.ended) return;
const messageSnapshot = this.#accumulateMessage(event);
this._emit('streamEvent', event, messageSnapshot);
switch (event.type) {
case 'content_block_delta': {
const content = messageSnapshot.content.at(-1)!;
switch (event.delta.type) {
case 'text_delta': {
if (content.type === 'text') {
this._emit('text', event.delta.text, content.text || '');
}
break;
}
case 'citations_delta': {
if (content.type === 'text') {
this._emit('citation', event.delta.citation, content.citations ?? []);
}
break;
}
case 'input_json_delta': {
if (tracksToolInput(content) && content.input) {
this._emit('inputJson', event.delta.partial_json, content.input);
}
break;
}
case 'thinking_delta': {
if (content.type === 'thinking') {
this._emit('thinking', event.delta.thinking, content.thinking);
}
break;
}
case 'signature_delta': {
if (content.type === 'thinking') {
this._emit('signature', content.signature);
}
break;
}
case 'compaction_delta': {
if (content.type === 'compaction' && content.content) {
this._emit('compaction', content.content);
}
break;
}
default:
checkNever(event.delta);
}
break;
}
case 'message_stop': {
this._addMessageParam(messageSnapshot);
this._addMessage(
maybeParseBetaMessage(messageSnapshot, this.#params, { logger: this.#logger }),
true,
);
break;
}
case 'content_block_stop': {
this._emit('contentBlock', messageSnapshot.content.at(-1)!);
break;
}
case 'message_start': {
this.#currentMessageSnapshot = messageSnapshot;
break;
}
case 'content_block_start':
case 'message_delta':
break;
}
}
#endRequest(): ParsedBetaMessage<ParsedT> {
if (this.ended) {
throw new AnthropicError(`stream has ended, this shouldn't happen`);
}
const snapshot = this.#currentMessageSnapshot;
if (!snapshot) {
throw new AnthropicError(`request ended without sending any chunks`);
}
this.#currentMessageSnapshot = undefined;
return maybeParseBetaMessage(snapshot, this.#params, { logger: this.#logger });
}
protected async _fromReadableStream(
readableStream: ReadableStream,
options?: RequestOptions,
): Promise<void> {
const signal = options?.signal;
let abortHandler: (() => void) | undefined;
if (signal) {
if (signal.aborted) this.controller.abort();
abortHandler = this.controller.abort.bind(this.controller);
signal.addEventListener('abort', abortHandler);
}
try {
this.#beginRequest();
this._connected(null);
const stream = Stream.fromReadableStream<BetaMessageStreamEvent>(readableStream, this.controller);
for await (const event of stream) {
this.#addStreamEvent(event);
}
if (stream.controller.signal?.aborted) {
throw new APIUserAbortError();
}
this.#endRequest();
} finally {
if (signal && abortHandler) {
signal.removeEventListener('abort', abortHandler);
}
}
}
/**
* Mutates this.#currentMessage with the current event. Handling the accumulation of multiple messages
* will be needed to be handled by the caller, this method will throw if you try to accumulate for multiple
* messages.
*/
#accumulateMessage(event: BetaMessageStreamEvent): BetaMessage {
let snapshot = this.#currentMessageSnapshot;
if (event.type === 'message_start') {
if (snapshot) {
throw new AnthropicError(`Unexpected event order, got ${event.type} before receiving "message_stop"`);
}
return event.message;
}
if (!snapshot) {
throw new AnthropicError(`Unexpected event order, got ${event.type} before "message_start"`);
}
switch (event.type) {
case 'message_stop':
return snapshot;
case 'message_delta':
snapshot.container = event.delta.container;
snapshot.stop_reason = event.delta.stop_reason;
snapshot.stop_sequence = event.delta.stop_sequence;
snapshot.usage.output_tokens = event.usage.output_tokens;
snapshot.context_management = event.context_management;
if (event.usage.input_tokens != null) {
snapshot.usage.input_tokens = event.usage.input_tokens;
}
if (event.usage.cache_creation_input_tokens != null) {
snapshot.usage.cache_creation_input_tokens = event.usage.cache_creation_input_tokens;
}
if (event.usage.cache_read_input_tokens != null) {
snapshot.usage.cache_read_input_tokens = event.usage.cache_read_input_tokens;
}
if (event.usage.server_tool_use != null) {
snapshot.usage.server_tool_use = event.usage.server_tool_use;
}
if (event.usage.iterations != null) {
snapshot.usage.iterations = event.usage.iterations;
}
return snapshot;
case 'content_block_start':
snapshot.content.push(event.content_block);
return snapshot;
case 'content_block_delta': {
const snapshotContent = snapshot.content.at(event.index);
switch (event.delta.type) {
case 'text_delta': {
if (snapshotContent?.type === 'text') {
snapshot.content[event.index] = {
...snapshotContent,
text: (snapshotContent.text || '') + event.delta.text,
};
}
break;
}
case 'citations_delta': {
if (snapshotContent?.type === 'text') {
snapshot.content[event.index] = {
...snapshotContent,
citations: [...(snapshotContent.citations ?? []), event.delta.citation],
};
}
break;
}
case 'input_json_delta': {
if (snapshotContent && tracksToolInput(snapshotContent)) {
// we need to keep track of the raw JSON string as well so that we can
// re-parse it for each delta, for now we just store it as an untyped
// non-enumerable property on the snapshot
let jsonBuf = (snapshotContent as any)[JSON_BUF_PROPERTY] || '';
jsonBuf += event.delta.partial_json;
const newContent = { ...snapshotContent };
Object.defineProperty(newContent, JSON_BUF_PROPERTY, {
value: jsonBuf,
enumerable: false,
writable: true,
});
if (jsonBuf) {
try {
newContent.input = partialParse(jsonBuf);
} catch (err) {
const error = new AnthropicError(
`Unable to parse tool parameter JSON from model. Please retry your request or adjust your prompt. Error: ${err}. JSON: ${jsonBuf}`,
);
this.#handleError(error);
}
}
snapshot.content[event.index] = newContent;
}
break;
}
case 'thinking_delta': {
if (snapshotContent?.type === 'thinking') {
snapshot.content[event.index] = {
...snapshotContent,
thinking: snapshotContent.thinking + event.delta.thinking,
};
}
break;
}
case 'signature_delta': {
if (snapshotContent?.type === 'thinking') {
snapshot.content[event.index] = {
...snapshotContent,
signature: event.delta.signature,
};
}
break;
}
case 'compaction_delta': {
if (snapshotContent?.type === 'compaction') {
snapshot.content[event.index] = {
...snapshotContent,
content: (snapshotContent.content || '') + event.delta.content,
};
}
break;
}
default:
checkNever(event.delta);
}
return snapshot;
}
case 'content_block_stop':
return snapshot;
}
}
[Symbol.asyncIterator](): AsyncIterator<BetaMessageStreamEvent> {
const pushQueue: BetaMessageStreamEvent[] = [];
const readQueue: {
resolve: (chunk: BetaMessageStreamEvent | undefined) => void;
reject: (error: unknown) => void;
}[] = [];
let done = false;
this.on('streamEvent', (event) => {
const reader = readQueue.shift();
if (reader) {
reader.resolve(event);
} else {
pushQueue.push(event);
}
});
this.on('end', () => {
done = true;
for (const reader of readQueue) {
reader.resolve(undefined);
}
readQueue.length = 0;
});
this.on('abort', (err) => {
done = true;
for (const reader of readQueue) {
reader.reject(err);
}
readQueue.length = 0;
});
this.on('error', (err) => {
done = true;
for (const reader of readQueue) {
reader.reject(err);
}
readQueue.length = 0;
});
return {
next: async (): Promise<IteratorResult<BetaMessageStreamEvent>> => {
if (!pushQueue.length) {
if (done) {
return { value: undefined, done: true };
}
return new Promise<BetaMessageStreamEvent | undefined>((resolve, reject) =>
readQueue.push({ resolve, reject }),
).then((chunk) => (chunk ? { value: chunk, done: false } : { value: undefined, done: true }));
}
const chunk = pushQueue.shift()!;
return { value: chunk, done: false };
},
return: async () => {
this.abort();
return { value: undefined, done: true };
},
};
}
toReadableStream(): ReadableStream {
const stream = new Stream(this[Symbol.asyncIterator].bind(this), this.controller);
return stream.toReadableStream();
}
}
// used to ensure exhaustive case matching without throwing a runtime error
function checkNever(x: never) {}

View File

@@ -0,0 +1,743 @@
import { isAbortError } from '../internal/errors';
import { AnthropicError, APIUserAbortError } from '../error';
import {
type ContentBlock,
Messages,
type Message,
type MessageStreamEvent,
type MessageParam,
type MessageCreateParams,
type MessageCreateParamsBase,
type TextBlock,
type TextCitation,
type ToolUseBlock,
type ServerToolUseBlock,
} from '../resources/messages';
import { Stream } from '../streaming';
import { partialParse } from '../_vendor/partial-json-parser/parser';
import { RequestOptions } from '../internal/request-options';
import type { Logger } from '../client';
import { maybeParseMessage, type ParsedMessage } from './parser';
export interface MessageStreamEvents<ParsedT = null> {
connect: () => void;
streamEvent: (event: MessageStreamEvent, snapshot: Message) => void;
text: (textDelta: string, textSnapshot: string) => void;
citation: (citation: TextCitation, citationsSnapshot: TextCitation[]) => void;
inputJson: (partialJson: string, jsonSnapshot: unknown) => void;
thinking: (thinkingDelta: string, thinkingSnapshot: string) => void;
signature: (signature: string) => void;
message: (message: ParsedMessage<ParsedT>) => void;
contentBlock: (content: ContentBlock) => void;
finalMessage: (message: ParsedMessage<ParsedT>) => void;
error: (error: AnthropicError) => void;
abort: (error: APIUserAbortError) => void;
end: () => void;
}
type MessageStreamEventListeners<ParsedT, Event extends keyof MessageStreamEvents<ParsedT>> = {
listener: MessageStreamEvents<ParsedT>[Event];
once?: boolean;
}[];
const JSON_BUF_PROPERTY = '__json_buf';
export type TracksToolInput = ToolUseBlock | ServerToolUseBlock;
function tracksToolInput(content: ContentBlock): content is TracksToolInput {
return content.type === 'tool_use' || content.type === 'server_tool_use';
}
export class MessageStream<ParsedT = null> implements AsyncIterable<MessageStreamEvent> {
messages: MessageParam[] = [];
receivedMessages: ParsedMessage<ParsedT>[] = [];
#currentMessageSnapshot: Message | undefined;
#params: MessageCreateParams | null = null;
controller: AbortController = new AbortController();
#connectedPromise: Promise<Response | null>;
#resolveConnectedPromise: (response: Response | null) => void = () => {};
#rejectConnectedPromise: (error: AnthropicError) => void = () => {};
#endPromise: Promise<void>;
#resolveEndPromise: () => void = () => {};
#rejectEndPromise: (error: AnthropicError) => void = () => {};
#listeners: {
[Event in keyof MessageStreamEvents<ParsedT>]?: MessageStreamEventListeners<ParsedT, Event>;
} = {};
#ended = false;
#errored = false;
#aborted = false;
#catchingPromiseCreated = false;
#response: Response | null | undefined;
#request_id: string | null | undefined;
#logger: Logger;
constructor(params: MessageCreateParamsBase | null, opts?: { logger?: Logger | undefined }) {
this.#connectedPromise = new Promise<Response | null>((resolve, reject) => {
this.#resolveConnectedPromise = resolve;
this.#rejectConnectedPromise = reject;
});
this.#endPromise = new Promise<void>((resolve, reject) => {
this.#resolveEndPromise = resolve;
this.#rejectEndPromise = reject;
});
// Don't let these promises cause unhandled rejection errors.
// we will manually cause an unhandled rejection error later
// if the user hasn't registered any error listener or called
// any promise-returning method.
this.#connectedPromise.catch(() => {});
this.#endPromise.catch(() => {});
this.#params = params;
this.#logger = opts?.logger ?? console;
}
get response(): Response | null | undefined {
return this.#response;
}
get request_id(): string | null | undefined {
return this.#request_id;
}
/**
* Returns the `MessageStream` data, the raw `Response` instance and the ID of the request,
* returned vie the `request-id` header which is useful for debugging requests and resporting
* issues to Anthropic.
*
* This is the same as the `APIPromise.withResponse()` method.
*
* This method will raise an error if you created the stream using `MessageStream.fromReadableStream`
* as no `Response` is available.
*/
async withResponse(): Promise<{
data: MessageStream<ParsedT>;
response: Response;
request_id: string | null | undefined;
}> {
this.#catchingPromiseCreated = true;
const response = await this.#connectedPromise;
if (!response) {
throw new Error('Could not resolve a `Response` object');
}
return {
data: this,
response,
request_id: response.headers.get('request-id'),
};
}
/**
* Intended for use on the frontend, consuming a stream produced with
* `.toReadableStream()` on the backend.
*
* Note that messages sent to the model do not appear in `.on('message')`
* in this context.
*/
static fromReadableStream(stream: ReadableStream): MessageStream {
const runner = new MessageStream(null);
runner._run(() => runner._fromReadableStream(stream));
return runner;
}
static createMessage<ParsedT>(
messages: Messages,
params: MessageCreateParamsBase,
options?: RequestOptions,
{ logger }: { logger?: Logger | undefined } = {},
): MessageStream<ParsedT> {
const runner = new MessageStream<ParsedT>(params, { logger });
for (const message of params.messages) {
runner._addMessageParam(message);
}
runner.#params = { ...params, stream: true };
runner._run(() =>
runner._createMessage(
messages,
{ ...params, stream: true },
{ ...options, headers: { ...options?.headers, 'X-Stainless-Helper-Method': 'stream' } },
),
);
return runner;
}
protected _run(executor: () => Promise<any>) {
executor().then(() => {
this._emitFinal();
this._emit('end');
}, this.#handleError);
}
protected _addMessageParam(message: MessageParam) {
this.messages.push(message);
}
protected _addMessage(message: ParsedMessage<ParsedT>, emit = true) {
this.receivedMessages.push(message);
if (emit) {
this._emit('message', message);
}
}
protected async _createMessage(
messages: Messages,
params: MessageCreateParams,
options?: RequestOptions,
): Promise<void> {
const signal = options?.signal;
let abortHandler: (() => void) | undefined;
if (signal) {
if (signal.aborted) this.controller.abort();
abortHandler = this.controller.abort.bind(this.controller);
signal.addEventListener('abort', abortHandler);
}
try {
this.#beginRequest();
const { response, data: stream } = await messages
.create({ ...params, stream: true }, { ...options, signal: this.controller.signal })
.withResponse();
this._connected(response);
for await (const event of stream) {
this.#addStreamEvent(event);
}
if (stream.controller.signal?.aborted) {
throw new APIUserAbortError();
}
this.#endRequest();
} finally {
if (signal && abortHandler) {
signal.removeEventListener('abort', abortHandler);
}
}
}
protected _connected(response: Response | null) {
if (this.ended) return;
this.#response = response;
this.#request_id = response?.headers.get('request-id');
this.#resolveConnectedPromise(response);
this._emit('connect');
}
get ended(): boolean {
return this.#ended;
}
get errored(): boolean {
return this.#errored;
}
get aborted(): boolean {
return this.#aborted;
}
abort() {
this.controller.abort();
}
/**
* Adds the listener function to the end of the listeners array for the event.
* No checks are made to see if the listener has already been added. Multiple calls passing
* the same combination of event and listener will result in the listener being added, and
* called, multiple times.
* @returns this MessageStream, so that calls can be chained
*/
on<Event extends keyof MessageStreamEvents<ParsedT>>(
event: Event,
listener: MessageStreamEvents<ParsedT>[Event],
): this {
const listeners: MessageStreamEventListeners<ParsedT, Event> =
this.#listeners[event] || (this.#listeners[event] = []);
listeners.push({ listener });
return this;
}
/**
* Removes the specified listener from the listener array for the event.
* off() will remove, at most, one instance of a listener from the listener array. If any single
* listener has been added multiple times to the listener array for the specified event, then
* off() must be called multiple times to remove each instance.
* @returns this MessageStream, so that calls can be chained
*/
off<Event extends keyof MessageStreamEvents<ParsedT>>(
event: Event,
listener: MessageStreamEvents<ParsedT>[Event],
): this {
const listeners = this.#listeners[event];
if (!listeners) return this;
const index = listeners.findIndex((l) => l.listener === listener);
if (index >= 0) listeners.splice(index, 1);
return this;
}
/**
* Adds a one-time listener function for the event. The next time the event is triggered,
* this listener is removed and then invoked.
* @returns this MessageStream, so that calls can be chained
*/
once<Event extends keyof MessageStreamEvents<ParsedT>>(
event: Event,
listener: MessageStreamEvents<ParsedT>[Event],
): this {
const listeners: MessageStreamEventListeners<ParsedT, Event> =
this.#listeners[event] || (this.#listeners[event] = []);
listeners.push({ listener, once: true });
return this;
}
/**
* This is similar to `.once()`, but returns a Promise that resolves the next time
* the event is triggered, instead of calling a listener callback.
* @returns a Promise that resolves the next time given event is triggered,
* or rejects if an error is emitted. (If you request the 'error' event,
* returns a promise that resolves with the error).
*
* Example:
*
* const message = await stream.emitted('message') // rejects if the stream errors
*/
emitted<Event extends keyof MessageStreamEvents<ParsedT>>(
event: Event,
): Promise<
Parameters<MessageStreamEvents<ParsedT>[Event]> extends [infer Param] ? Param
: Parameters<MessageStreamEvents<ParsedT>[Event]> extends [] ? void
: Parameters<MessageStreamEvents<ParsedT>[Event]>
> {
return new Promise((resolve, reject) => {
this.#catchingPromiseCreated = true;
if (event !== 'error') this.once('error', reject);
this.once(event, resolve as any);
});
}
async done(): Promise<void> {
this.#catchingPromiseCreated = true;
await this.#endPromise;
}
get currentMessage(): Message | undefined {
return this.#currentMessageSnapshot;
}
#getFinalMessage(): ParsedMessage<ParsedT> {
if (this.receivedMessages.length === 0) {
throw new AnthropicError('stream ended without producing a Message with role=assistant');
}
return this.receivedMessages.at(-1)!;
}
/**
* @returns a promise that resolves with the the final assistant Message response,
* or rejects if an error occurred or the stream ended prematurely without producing a Message.
* If structured outputs were used, this will be a ParsedMessage with a `parsed_output` field.
*/
async finalMessage(): Promise<ParsedMessage<ParsedT>> {
await this.done();
return this.#getFinalMessage();
}
#getFinalText(): string {
if (this.receivedMessages.length === 0) {
throw new AnthropicError('stream ended without producing a Message with role=assistant');
}
const textBlocks = this.receivedMessages
.at(-1)!
.content.filter((block): block is TextBlock => block.type === 'text')
.map((block) => block.text);
if (textBlocks.length === 0) {
throw new AnthropicError('stream ended without producing a content block with type=text');
}
return textBlocks.join(' ');
}
/**
* @returns a promise that resolves with the the final assistant Message's text response, concatenated
* together if there are more than one text blocks.
* Rejects if an error occurred or the stream ended prematurely without producing a Message.
*/
async finalText(): Promise<string> {
await this.done();
return this.#getFinalText();
}
#handleError = (error: unknown) => {
this.#errored = true;
if (isAbortError(error)) {
error = new APIUserAbortError();
}
if (error instanceof APIUserAbortError) {
this.#aborted = true;
return this._emit('abort', error);
}
if (error instanceof AnthropicError) {
return this._emit('error', error);
}
if (error instanceof Error) {
const anthropicError: AnthropicError = new AnthropicError(error.message);
// @ts-ignore
anthropicError.cause = error;
return this._emit('error', anthropicError);
}
return this._emit('error', new AnthropicError(String(error)));
};
protected _emit<Event extends keyof MessageStreamEvents<ParsedT>>(
event: Event,
...args: Parameters<MessageStreamEvents<ParsedT>[Event]>
) {
// make sure we don't emit any MessageStreamEvents after end
if (this.#ended) return;
if (event === 'end') {
this.#ended = true;
this.#resolveEndPromise();
}
const listeners: MessageStreamEventListeners<ParsedT, Event> | undefined = this.#listeners[event];
if (listeners) {
this.#listeners[event] = listeners.filter((l: { once?: boolean }) => !l.once) as any;
listeners.forEach(({ listener }: any) => listener(...args));
}
if (event === 'abort') {
const error = args[0] as APIUserAbortError;
if (!this.#catchingPromiseCreated && !listeners?.length) {
Promise.reject(error);
}
this.#rejectConnectedPromise(error);
this.#rejectEndPromise(error);
this._emit('end');
return;
}
if (event === 'error') {
// NOTE: _emit('error', error) should only be called from #handleError().
const error = args[0] as AnthropicError;
if (!this.#catchingPromiseCreated && !listeners?.length) {
// Trigger an unhandled rejection if the user hasn't registered any error handlers.
// If you are seeing stack traces here, make sure to handle errors via either:
// - runner.on('error', () => ...)
// - await runner.done()
// - await runner.final...()
// - etc.
Promise.reject(error);
}
this.#rejectConnectedPromise(error);
this.#rejectEndPromise(error);
this._emit('end');
}
}
protected _emitFinal() {
const finalMessage = this.receivedMessages.at(-1);
if (finalMessage) {
this._emit('finalMessage', this.#getFinalMessage());
}
}
#beginRequest() {
if (this.ended) return;
this.#currentMessageSnapshot = undefined;
}
#addStreamEvent(event: MessageStreamEvent) {
if (this.ended) return;
const messageSnapshot = this.#accumulateMessage(event);
this._emit('streamEvent', event, messageSnapshot);
switch (event.type) {
case 'content_block_delta': {
const content = messageSnapshot.content.at(-1)!;
switch (event.delta.type) {
case 'text_delta': {
if (content.type === 'text') {
this._emit('text', event.delta.text, content.text || '');
}
break;
}
case 'citations_delta': {
if (content.type === 'text') {
this._emit('citation', event.delta.citation, content.citations ?? []);
}
break;
}
case 'input_json_delta': {
if (tracksToolInput(content) && content.input) {
this._emit('inputJson', event.delta.partial_json, content.input);
}
break;
}
case 'thinking_delta': {
if (content.type === 'thinking') {
this._emit('thinking', event.delta.thinking, content.thinking);
}
break;
}
case 'signature_delta': {
if (content.type === 'thinking') {
this._emit('signature', content.signature);
}
break;
}
default:
checkNever(event.delta);
}
break;
}
case 'message_stop': {
this._addMessageParam(messageSnapshot);
this._addMessage(maybeParseMessage(messageSnapshot, this.#params, { logger: this.#logger }), true);
break;
}
case 'content_block_stop': {
this._emit('contentBlock', messageSnapshot.content.at(-1)!);
break;
}
case 'message_start': {
this.#currentMessageSnapshot = messageSnapshot;
break;
}
case 'content_block_start':
case 'message_delta':
break;
}
}
#endRequest(): ParsedMessage<ParsedT> {
if (this.ended) {
throw new AnthropicError(`stream has ended, this shouldn't happen`);
}
const snapshot = this.#currentMessageSnapshot;
if (!snapshot) {
throw new AnthropicError(`request ended without sending any chunks`);
}
this.#currentMessageSnapshot = undefined;
return maybeParseMessage(snapshot, this.#params, { logger: this.#logger });
}
protected async _fromReadableStream(
readableStream: ReadableStream,
options?: RequestOptions,
): Promise<void> {
const signal = options?.signal;
let abortHandler: (() => void) | undefined;
if (signal) {
if (signal.aborted) this.controller.abort();
abortHandler = this.controller.abort.bind(this.controller);
signal.addEventListener('abort', abortHandler);
}
try {
this.#beginRequest();
this._connected(null);
const stream = Stream.fromReadableStream<MessageStreamEvent>(readableStream, this.controller);
for await (const event of stream) {
this.#addStreamEvent(event);
}
if (stream.controller.signal?.aborted) {
throw new APIUserAbortError();
}
this.#endRequest();
} finally {
if (signal && abortHandler) {
signal.removeEventListener('abort', abortHandler);
}
}
}
/**
* Mutates this.#currentMessage with the current event. Handling the accumulation of multiple messages
* will be needed to be handled by the caller, this method will throw if you try to accumulate for multiple
* messages.
*/
#accumulateMessage(event: MessageStreamEvent): Message {
let snapshot = this.#currentMessageSnapshot;
if (event.type === 'message_start') {
if (snapshot) {
throw new AnthropicError(`Unexpected event order, got ${event.type} before receiving "message_stop"`);
}
return event.message;
}
if (!snapshot) {
throw new AnthropicError(`Unexpected event order, got ${event.type} before "message_start"`);
}
switch (event.type) {
case 'message_stop':
return snapshot;
case 'message_delta':
snapshot.stop_reason = event.delta.stop_reason;
snapshot.stop_sequence = event.delta.stop_sequence;
snapshot.usage.output_tokens = event.usage.output_tokens;
// Update other usage fields if they exist in the event
if (event.usage.input_tokens != null) {
snapshot.usage.input_tokens = event.usage.input_tokens;
}
if (event.usage.cache_creation_input_tokens != null) {
snapshot.usage.cache_creation_input_tokens = event.usage.cache_creation_input_tokens;
}
if (event.usage.cache_read_input_tokens != null) {
snapshot.usage.cache_read_input_tokens = event.usage.cache_read_input_tokens;
}
if (event.usage.server_tool_use != null) {
snapshot.usage.server_tool_use = event.usage.server_tool_use;
}
return snapshot;
case 'content_block_start':
snapshot.content.push({ ...event.content_block });
return snapshot;
case 'content_block_delta': {
const snapshotContent = snapshot.content.at(event.index);
switch (event.delta.type) {
case 'text_delta': {
if (snapshotContent?.type === 'text') {
snapshot.content[event.index] = {
...snapshotContent,
text: (snapshotContent.text || '') + event.delta.text,
};
}
break;
}
case 'citations_delta': {
if (snapshotContent?.type === 'text') {
snapshot.content[event.index] = {
...snapshotContent,
citations: [...(snapshotContent.citations ?? []), event.delta.citation],
};
}
break;
}
case 'input_json_delta': {
if (snapshotContent && tracksToolInput(snapshotContent)) {
// we need to keep track of the raw JSON string as well so that we can
// re-parse it for each delta, for now we just store it as an untyped
// non-enumerable property on the snapshot
let jsonBuf = (snapshotContent as any)[JSON_BUF_PROPERTY] || '';
jsonBuf += event.delta.partial_json;
const newContent = { ...snapshotContent };
Object.defineProperty(newContent, JSON_BUF_PROPERTY, {
value: jsonBuf,
enumerable: false,
writable: true,
});
if (jsonBuf) {
newContent.input = partialParse(jsonBuf);
}
snapshot.content[event.index] = newContent;
}
break;
}
case 'thinking_delta': {
if (snapshotContent?.type === 'thinking') {
snapshot.content[event.index] = {
...snapshotContent,
thinking: snapshotContent.thinking + event.delta.thinking,
};
}
break;
}
case 'signature_delta': {
if (snapshotContent?.type === 'thinking') {
snapshot.content[event.index] = {
...snapshotContent,
signature: event.delta.signature,
};
}
break;
}
default:
checkNever(event.delta);
}
return snapshot;
}
case 'content_block_stop':
return snapshot;
}
}
[Symbol.asyncIterator](): AsyncIterator<MessageStreamEvent> {
const pushQueue: MessageStreamEvent[] = [];
const readQueue: {
resolve: (chunk: MessageStreamEvent | undefined) => void;
reject: (error: unknown) => void;
}[] = [];
let done = false;
this.on('streamEvent', (event) => {
const reader = readQueue.shift();
if (reader) {
reader.resolve(event);
} else {
pushQueue.push(event);
}
});
this.on('end', () => {
done = true;
for (const reader of readQueue) {
reader.resolve(undefined);
}
readQueue.length = 0;
});
this.on('abort', (err) => {
done = true;
for (const reader of readQueue) {
reader.reject(err);
}
readQueue.length = 0;
});
this.on('error', (err) => {
done = true;
for (const reader of readQueue) {
reader.reject(err);
}
readQueue.length = 0;
});
return {
next: async (): Promise<IteratorResult<MessageStreamEvent>> => {
if (!pushQueue.length) {
if (done) {
return { value: undefined, done: true };
}
return new Promise<MessageStreamEvent | undefined>((resolve, reject) =>
readQueue.push({ resolve, reject }),
).then((chunk) => (chunk ? { value: chunk, done: false } : { value: undefined, done: true }));
}
const chunk = pushQueue.shift()!;
return { value: chunk, done: false };
},
return: async () => {
this.abort();
return { value: undefined, done: true };
},
};
}
toReadableStream(): ReadableStream {
const stream = new Stream(this[Symbol.asyncIterator].bind(this), this.controller);
return stream.toReadableStream();
}
}
// used to ensure exhaustive case matching without throwing a runtime error
function checkNever(x: never) {}

View File

@@ -0,0 +1,148 @@
import type { Logger } from '../client';
import { AnthropicError } from '../core/error';
import {
BetaContentBlock,
BetaJSONOutputFormat,
BetaMessage,
BetaOutputConfig,
BetaTextBlock,
MessageCreateParams,
} from '../resources/beta/messages/messages';
// vendored from typefest just to make things look a bit nicer on hover
type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {};
type AutoParseableBetaOutputConfig = Omit<BetaOutputConfig, 'format'> & {
format?: BetaJSONOutputFormat | AutoParseableBetaOutputFormat<any> | null;
};
export type BetaParseableMessageCreateParams = Simplify<
Omit<MessageCreateParams, 'output_format' | 'output_config'> & {
/**
* @deprecated Use `output_config.format` instead. This parameter will be removed in a future
* release.
*/
output_format?: BetaJSONOutputFormat | AutoParseableBetaOutputFormat<any> | null;
output_config?: AutoParseableBetaOutputConfig | null;
}
>;
export type ExtractParsedContentFromBetaParams<Params extends BetaParseableMessageCreateParams> =
Params['output_format'] extends AutoParseableBetaOutputFormat<infer P> ? P
: Params['output_config'] extends { format: AutoParseableBetaOutputFormat<infer P> } ? P
: null;
export type AutoParseableBetaOutputFormat<ParsedT> = BetaJSONOutputFormat & {
parse(content: string): ParsedT;
};
export type ParsedBetaMessage<ParsedT> = BetaMessage & {
content: Array<ParsedBetaContentBlock<ParsedT>>;
parsed_output: ParsedT | null;
};
export type ParsedBetaContentBlock<ParsedT> =
| (BetaTextBlock & { parsed_output: ParsedT | null })
| Exclude<BetaContentBlock, BetaTextBlock>;
function getOutputFormat(
params: BetaParseableMessageCreateParams | null,
): BetaJSONOutputFormat | AutoParseableBetaOutputFormat<any> | null | undefined {
// Prefer output_format (deprecated) over output_config.format for backward compatibility
return params?.output_format ?? params?.output_config?.format;
}
export function maybeParseBetaMessage<Params extends BetaParseableMessageCreateParams | null>(
message: BetaMessage,
params: Params,
opts: { logger: Logger },
): ParsedBetaMessage<ExtractParsedContentFromBetaParams<NonNullable<Params>>> {
const outputFormat = getOutputFormat(params);
if (!params || !('parse' in (outputFormat ?? {}))) {
return {
...message,
content: message.content.map((block) => {
if (block.type === 'text') {
const parsedBlock = Object.defineProperty({ ...block }, 'parsed_output', {
value: null,
enumerable: false,
}) as ParsedBetaContentBlock<ExtractParsedContentFromBetaParams<NonNullable<Params>>>;
return Object.defineProperty(parsedBlock, 'parsed', {
get() {
opts.logger.warn(
'The `parsed` property on `text` blocks is deprecated, please use `parsed_output` instead.',
);
return null;
},
enumerable: false,
});
}
return block;
}),
parsed_output: null,
} as ParsedBetaMessage<ExtractParsedContentFromBetaParams<NonNullable<Params>>>;
}
return parseBetaMessage(message, params, opts);
}
export function parseBetaMessage<Params extends BetaParseableMessageCreateParams>(
message: BetaMessage,
params: Params,
opts: { logger: Logger },
): ParsedBetaMessage<ExtractParsedContentFromBetaParams<Params>> {
let firstParsedOutput: ReturnType<typeof parseBetaOutputFormat<Params>> | null = null;
const content: Array<ParsedBetaContentBlock<ExtractParsedContentFromBetaParams<Params>>> =
message.content.map((block) => {
if (block.type === 'text') {
const parsedOutput = parseBetaOutputFormat(params, block.text);
if (firstParsedOutput === null) {
firstParsedOutput = parsedOutput;
}
const parsedBlock = Object.defineProperty({ ...block }, 'parsed_output', {
value: parsedOutput,
enumerable: false,
}) as ParsedBetaContentBlock<ExtractParsedContentFromBetaParams<Params>>;
return Object.defineProperty(parsedBlock, 'parsed', {
get() {
opts.logger.warn(
'The `parsed` property on `text` blocks is deprecated, please use `parsed_output` instead.',
);
return parsedOutput;
},
enumerable: false,
});
}
return block;
});
return {
...message,
content,
parsed_output: firstParsedOutput,
} as ParsedBetaMessage<ExtractParsedContentFromBetaParams<Params>>;
}
function parseBetaOutputFormat<Params extends BetaParseableMessageCreateParams>(
params: Params,
content: string,
): ExtractParsedContentFromBetaParams<Params> | null {
const outputFormat = getOutputFormat(params);
if (outputFormat?.type !== 'json_schema') {
return null;
}
try {
if ('parse' in outputFormat) {
return outputFormat.parse(content);
}
return JSON.parse(content);
} catch (error) {
throw new AnthropicError(`Failed to parse structured output: ${error}`);
}
}

View File

@@ -0,0 +1,125 @@
import type { Logger } from '../client';
import { AnthropicError } from '../core/error';
import {
ContentBlock,
JSONOutputFormat,
Message,
OutputConfig,
TextBlock,
MessageCreateParams,
} from '../resources/messages/messages';
// vendored from typefest just to make things look a bit nicer on hover
type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {};
type AutoParseableOutputConfig = Omit<OutputConfig, 'format'> & {
format?: JSONOutputFormat | AutoParseableOutputFormat<any> | null;
};
export type ParseableMessageCreateParams = Simplify<
Omit<MessageCreateParams, 'output_config'> & {
output_config?: AutoParseableOutputConfig | null;
}
>;
export type ExtractParsedContentFromParams<Params extends ParseableMessageCreateParams> =
Params['output_config'] extends { format: AutoParseableOutputFormat<infer P> } ? P : null;
export type AutoParseableOutputFormat<ParsedT> = JSONOutputFormat & {
parse(content: string): ParsedT;
};
export type ParsedMessage<ParsedT> = Message & {
content: Array<ParsedContentBlock<ParsedT>>;
parsed_output: ParsedT | null;
};
export type ParsedContentBlock<ParsedT> =
| (TextBlock & { parsed_output: ParsedT | null })
| Exclude<ContentBlock, TextBlock>;
function getOutputFormat(
params: ParseableMessageCreateParams | null,
): JSONOutputFormat | AutoParseableOutputFormat<any> | null | undefined {
return params?.output_config?.format;
}
export function maybeParseMessage<Params extends ParseableMessageCreateParams | null>(
message: Message,
params: Params,
opts: { logger: Logger },
): ParsedMessage<ExtractParsedContentFromParams<NonNullable<Params>>> {
const outputFormat = getOutputFormat(params);
if (!params || !('parse' in (outputFormat ?? {}))) {
return {
...message,
content: message.content.map((block) => {
if (block.type === 'text') {
const parsedBlock = Object.defineProperty({ ...block }, 'parsed_output', {
value: null,
enumerable: false,
}) as ParsedContentBlock<ExtractParsedContentFromParams<NonNullable<Params>>>;
return parsedBlock;
}
return block;
}),
parsed_output: null,
} as ParsedMessage<ExtractParsedContentFromParams<NonNullable<Params>>>;
}
return parseMessage(message, params, opts);
}
export function parseMessage<Params extends ParseableMessageCreateParams>(
message: Message,
params: Params,
opts: { logger: Logger },
): ParsedMessage<ExtractParsedContentFromParams<Params>> {
let firstParsedOutput: ReturnType<typeof parseOutputFormat<Params>> | null = null;
const content: Array<ParsedContentBlock<ExtractParsedContentFromParams<Params>>> = message.content.map(
(block) => {
if (block.type === 'text') {
const parsedOutput = parseOutputFormat(params, block.text);
if (firstParsedOutput === null) {
firstParsedOutput = parsedOutput;
}
const parsedBlock = Object.defineProperty({ ...block }, 'parsed_output', {
value: parsedOutput,
enumerable: false,
}) as ParsedContentBlock<ExtractParsedContentFromParams<Params>>;
return parsedBlock;
}
return block;
},
);
return {
...message,
content,
parsed_output: firstParsedOutput,
} as ParsedMessage<ExtractParsedContentFromParams<Params>>;
}
function parseOutputFormat<Params extends ParseableMessageCreateParams>(
params: Params,
content: string,
): ExtractParsedContentFromParams<Params> | null {
const outputFormat = getOutputFormat(params);
if (outputFormat?.type !== 'json_schema') {
return null;
}
try {
if ('parse' in outputFormat) {
return outputFormat.parse(content);
}
return JSON.parse(content);
} catch (error) {
throw new AnthropicError(`Failed to parse structured output: ${error}`);
}
}

View File

@@ -0,0 +1,80 @@
/**
* Shared utilities for tracking SDK helper usage.
*/
import type { BetaMessageParam, BetaToolUnion } from '../resources/beta';
/**
* Symbol used to mark objects created by SDK helpers for tracking.
* The value is the helper name (e.g., 'mcpTool', 'betaZodTool').
*/
export const SDK_HELPER_SYMBOL = Symbol('anthropic.sdk.stainlessHelper');
type StainlessHelperObject = { [SDK_HELPER_SYMBOL]: string };
export function wasCreatedByStainlessHelper(value: unknown): value is StainlessHelperObject {
return typeof value === 'object' && value !== null && SDK_HELPER_SYMBOL in value;
}
/**
* Collects helper names from tools and messages arrays.
* Returns a deduplicated array of helper names found.
*/
export function collectStainlessHelpers(
tools: BetaToolUnion[] | undefined,
messages: BetaMessageParam[] | undefined,
): string[] {
const helpers = new Set<string>();
// Collect from tools
if (tools) {
for (const tool of tools) {
if (wasCreatedByStainlessHelper(tool)) {
helpers.add(tool[SDK_HELPER_SYMBOL]);
}
}
}
// Collect from messages and their content blocks
if (messages) {
for (const message of messages) {
if (wasCreatedByStainlessHelper(message)) {
helpers.add(message[SDK_HELPER_SYMBOL]);
}
if (Array.isArray(message.content)) {
for (const block of message.content) {
if (wasCreatedByStainlessHelper(block)) {
helpers.add(block[SDK_HELPER_SYMBOL]);
}
}
}
}
}
return Array.from(helpers);
}
/**
* Builds x-stainless-helper header value from tools and messages.
* Returns an empty object if no helpers are found.
*/
export function stainlessHelperHeader(
tools: BetaToolUnion[] | undefined,
messages: BetaMessageParam[] | undefined,
): { 'x-stainless-helper'?: string } {
const helpers = collectStainlessHelpers(tools, messages);
if (helpers.length === 0) return {};
return { 'x-stainless-helper': helpers.join(', ') };
}
/**
* Builds x-stainless-helper header value from a file object.
* Returns an empty object if the file is not marked with a helper.
*/
export function stainlessHelperHeaderFromFile(file: unknown): { 'x-stainless-helper'?: string } {
if (wasCreatedByStainlessHelper(file)) {
return { 'x-stainless-helper': file[SDK_HELPER_SYMBOL] };
}
return {};
}

View File

@@ -0,0 +1,40 @@
import {
BetaMemoryTool20250818,
BetaTool,
BetaToolBash20241022,
BetaToolBash20250124,
BetaToolComputerUse20241022,
BetaToolComputerUse20250124,
BetaToolComputerUse20251124,
BetaToolResultContentBlockParam,
BetaToolTextEditor20241022,
BetaToolTextEditor20250124,
BetaToolTextEditor20250429,
BetaToolTextEditor20250728,
} from '../../resources/beta';
export type Promisable<T> = T | Promise<T>;
/**
* Tool types that can be implemented on the client.
* Excludes server-side tools like code execution, web search, and MCP toolsets.
*/
export type BetaClientRunnableToolType =
| BetaTool
| BetaMemoryTool20250818
| BetaToolBash20241022
| BetaToolBash20250124
| BetaToolComputerUse20241022
| BetaToolComputerUse20250124
| BetaToolComputerUse20251124
| BetaToolTextEditor20241022
| BetaToolTextEditor20250124
| BetaToolTextEditor20250429
| BetaToolTextEditor20250728;
// this type is just an extension of BetaTool with a run and parse method
// that will be called by `toolRunner()` helpers
export type BetaRunnableTool<Input = any> = BetaClientRunnableToolType & {
run: (args: Input) => Promisable<string | Array<BetaToolResultContentBlockParam>>;
parse: (content: unknown) => Input;
};

View File

@@ -0,0 +1,481 @@
import { BetaRunnableTool } from './BetaRunnableTool';
import { ToolError } from './ToolError';
import { Anthropic } from '../..';
import { AnthropicError } from '../../core/error';
import { BetaMessage, BetaMessageParam, BetaToolUnion, MessageCreateParams } from '../../resources/beta';
import { BetaMessageStream } from '../BetaMessageStream';
import { RequestOptions } from '../../internal/request-options';
import { buildHeaders } from '../../internal/headers';
import { CompactionControl, DEFAULT_SUMMARY_PROMPT, DEFAULT_TOKEN_THRESHOLD } from './CompactionControl';
import { collectStainlessHelpers } from '../stainless-helper-header';
/**
* Just Promise.withResolvers(), which is not available in all environments.
*/
function promiseWithResolvers<T>(): {
promise: Promise<T>;
resolve: (value: T) => void;
reject: (reason?: any) => void;
} {
let resolve: (value: T) => void;
let reject: (reason?: any) => void;
const promise = new Promise<T>((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve: resolve!, reject: reject! };
}
/**
* A ToolRunner handles the automatic conversation loop between the assistant and tools.
*
* A ToolRunner is an async iterable that yields either BetaMessage or BetaMessageStream objects
* depending on the streaming configuration.
*/
export class BetaToolRunner<Stream extends boolean> {
/** Whether the async iterator has been consumed */
#consumed = false;
/** Whether parameters have been mutated since the last API call */
#mutated = false;
/** Current state containing the request parameters */
#state: { params: BetaToolRunnerParams };
#options: BetaToolRunnerRequestOptions;
/** Promise for the last message received from the assistant */
#message?: Promise<BetaMessage> | undefined;
/** Cached tool response to avoid redundant executions */
#toolResponse?: Promise<BetaMessageParam | null> | undefined;
/** Promise resolvers for waiting on completion */
#completion: {
promise: Promise<BetaMessage>;
resolve: (value: BetaMessage) => void;
reject: (reason?: any) => void;
};
/** Number of iterations (API requests) made so far */
#iterationCount = 0;
constructor(
private client: Anthropic,
params: BetaToolRunnerParams,
options?: BetaToolRunnerRequestOptions,
) {
this.#state = {
params: {
// You can't clone the entire params since there are functions as handlers.
// You also don't really need to clone params.messages, but it probably will prevent a foot gun
// somewhere.
...params,
messages: structuredClone(params.messages),
},
};
const helpers = collectStainlessHelpers(params.tools, params.messages);
const helperValue = ['BetaToolRunner', ...helpers].join(', ');
this.#options = {
...options,
headers: buildHeaders([{ 'x-stainless-helper': helperValue }, options?.headers]),
};
this.#completion = promiseWithResolvers();
}
async #checkAndCompact(): Promise<boolean> {
const compactionControl = this.#state.params.compactionControl;
if (!compactionControl || !compactionControl.enabled) {
return false;
}
let tokensUsed = 0;
if (this.#message !== undefined) {
try {
const message = await this.#message;
const totalInputTokens =
message.usage.input_tokens +
(message.usage.cache_creation_input_tokens ?? 0) +
(message.usage.cache_read_input_tokens ?? 0);
tokensUsed = totalInputTokens + message.usage.output_tokens;
} catch {
// If we can't get the message, skip compaction
return false;
}
}
const threshold = compactionControl.contextTokenThreshold ?? DEFAULT_TOKEN_THRESHOLD;
if (tokensUsed < threshold) {
return false;
}
const model = compactionControl.model ?? this.#state.params.model;
const summaryPrompt = compactionControl.summaryPrompt ?? DEFAULT_SUMMARY_PROMPT;
const messages = this.#state.params.messages;
if (messages[messages.length - 1]!.role === 'assistant') {
// Remove tool_use blocks from the last message to avoid 400 error
// (tool_use requires tool_result, which we don't have yet)
const lastMessage = messages[messages.length - 1]!;
if (Array.isArray(lastMessage.content)) {
const nonToolBlocks = lastMessage.content.filter((block) => block.type !== 'tool_use');
if (nonToolBlocks.length === 0) {
// If all blocks were tool_use, just remove the message entirely
messages.pop();
} else {
lastMessage.content = nonToolBlocks;
}
}
}
const response = await this.client.beta.messages.create(
{
model,
messages: [
...messages,
{
role: 'user',
content: [
{
type: 'text',
text: summaryPrompt,
},
],
},
],
max_tokens: this.#state.params.max_tokens,
},
{
headers: { 'x-stainless-helper': 'compaction' },
},
);
if (response.content[0]?.type !== 'text') {
throw new AnthropicError('Expected text response for compaction');
}
this.#state.params.messages = [
{
role: 'user',
content: response.content,
},
];
return true;
}
async *[Symbol.asyncIterator](): AsyncIterator<
Stream extends true ? BetaMessageStream
: Stream extends false ? BetaMessage
: BetaMessage | BetaMessageStream
> {
if (this.#consumed) {
throw new AnthropicError('Cannot iterate over a consumed stream');
}
this.#consumed = true;
this.#mutated = true;
this.#toolResponse = undefined;
try {
while (true) {
let stream;
try {
if (
this.#state.params.max_iterations &&
this.#iterationCount >= this.#state.params.max_iterations
) {
break;
}
this.#mutated = false;
this.#toolResponse = undefined;
this.#iterationCount++;
this.#message = undefined;
const { max_iterations, compactionControl, ...params } = this.#state.params;
if (params.stream) {
stream = this.client.beta.messages.stream({ ...params }, this.#options);
this.#message = stream.finalMessage();
// Make sure that this promise doesn't throw before we get the option to do something about it.
// Error will be caught when we call await this.#message ultimately
this.#message.catch(() => {});
yield stream as any;
} else {
this.#message = this.client.beta.messages.create({ ...params, stream: false }, this.#options);
yield this.#message as any;
}
const isCompacted = await this.#checkAndCompact();
if (!isCompacted) {
if (!this.#mutated) {
const { role, content } = await this.#message;
this.#state.params.messages.push({ role, content });
}
const toolMessage = await this.#generateToolResponse(this.#state.params.messages.at(-1)!);
if (toolMessage) {
this.#state.params.messages.push(toolMessage);
} else if (!this.#mutated) {
break;
}
}
} finally {
if (stream) {
stream.abort();
}
}
}
if (!this.#message) {
throw new AnthropicError('ToolRunner concluded without a message from the server');
}
this.#completion.resolve(await this.#message);
} catch (error) {
this.#consumed = false;
// Silence unhandled promise errors
this.#completion.promise.catch(() => {});
this.#completion.reject(error);
this.#completion = promiseWithResolvers();
throw error;
}
}
/**
* Update the parameters for the next API call. This invalidates any cached tool responses.
*
* @param paramsOrMutator - Either new parameters or a function to mutate existing parameters
*
* @example
* // Direct parameter update
* runner.setMessagesParams({
* model: 'claude-haiku-4-5',
* max_tokens: 500,
* });
*
* @example
* // Using a mutator function
* runner.setMessagesParams((params) => ({
* ...params,
* max_tokens: 100,
* }));
*/
setMessagesParams(params: BetaToolRunnerParams): void;
setMessagesParams(mutator: (prevParams: BetaToolRunnerParams) => BetaToolRunnerParams): void;
setMessagesParams(
paramsOrMutator: BetaToolRunnerParams | ((prevParams: BetaToolRunnerParams) => BetaToolRunnerParams),
) {
if (typeof paramsOrMutator === 'function') {
this.#state.params = paramsOrMutator(this.#state.params);
} else {
this.#state.params = paramsOrMutator;
}
this.#mutated = true;
// Invalidate cached tool response since parameters changed
this.#toolResponse = undefined;
}
/**
* Get the tool response for the last message from the assistant.
* Avoids redundant tool executions by caching results.
*
* @returns A promise that resolves to a BetaMessageParam containing tool results, or null if no tools need to be executed
*
* @example
* const toolResponse = await runner.generateToolResponse();
* if (toolResponse) {
* console.log('Tool results:', toolResponse.content);
* }
*/
async generateToolResponse() {
const message = (await this.#message) ?? this.params.messages.at(-1);
if (!message) {
return null;
}
return this.#generateToolResponse(message);
}
async #generateToolResponse(lastMessage: BetaMessageParam) {
if (this.#toolResponse !== undefined) {
return this.#toolResponse;
}
this.#toolResponse = generateToolResponse(this.#state.params, lastMessage);
return this.#toolResponse;
}
/**
* Wait for the async iterator to complete. This works even if the async iterator hasn't yet started, and
* will wait for an instance to start and go to completion.
*
* @returns A promise that resolves to the final BetaMessage when the iterator completes
*
* @example
* // Start consuming the iterator
* for await (const message of runner) {
* console.log('Message:', message.content);
* }
*
* // Meanwhile, wait for completion from another part of the code
* const finalMessage = await runner.done();
* console.log('Final response:', finalMessage.content);
*/
done(): Promise<BetaMessage> {
return this.#completion.promise;
}
/**
* Returns a promise indicating that the stream is done. Unlike .done(), this will eagerly read the stream:
* * If the iterator has not been consumed, consume the entire iterator and return the final message from the
* assistant.
* * If the iterator has been consumed, waits for it to complete and returns the final message.
*
* @returns A promise that resolves to the final BetaMessage from the conversation
* @throws {AnthropicError} If no messages were processed during the conversation
*
* @example
* const finalMessage = await runner.runUntilDone();
* console.log('Final response:', finalMessage.content);
*/
async runUntilDone(): Promise<BetaMessage> {
// If not yet consumed, start consuming and wait for completion
if (!this.#consumed) {
for await (const _ of this) {
// Iterator naturally populates this.#message
}
}
// If consumed but not completed, wait for completion
return this.done();
}
/**
* Get the current parameters being used by the ToolRunner.
*
* @returns A readonly view of the current ToolRunnerParams
*
* @example
* const currentParams = runner.params;
* console.log('Current model:', currentParams.model);
* console.log('Message count:', currentParams.messages.length);
*/
get params(): Readonly<BetaToolRunnerParams> {
return this.#state.params as Readonly<BetaToolRunnerParams>;
}
/**
* Add one or more messages to the conversation history.
*
* @param messages - One or more BetaMessageParam objects to add to the conversation
*
* @example
* runner.pushMessages(
* { role: 'user', content: 'Also, what about the weather in NYC?' }
* );
*
* @example
* // Adding multiple messages
* runner.pushMessages(
* { role: 'user', content: 'What about NYC?' },
* { role: 'user', content: 'And Boston?' }
* );
*/
pushMessages(...messages: BetaMessageParam[]) {
this.setMessagesParams((params) => ({
...params,
messages: [...params.messages, ...messages],
}));
}
/**
* Makes the ToolRunner directly awaitable, equivalent to calling .runUntilDone()
* This allows using `await runner` instead of `await runner.runUntilDone()`
*/
then<TResult1 = BetaMessage, TResult2 = never>(
onfulfilled?: ((value: BetaMessage) => TResult1 | PromiseLike<TResult1>) | undefined | null,
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null,
): Promise<TResult1 | TResult2> {
return this.runUntilDone().then(onfulfilled, onrejected);
}
}
async function generateToolResponse(
params: BetaToolRunnerParams,
lastMessage = params.messages.at(-1),
): Promise<BetaMessageParam | null> {
// Only process if the last message is from the assistant and has tool use blocks
if (
!lastMessage ||
lastMessage.role !== 'assistant' ||
!lastMessage.content ||
typeof lastMessage.content === 'string'
) {
return null;
}
const toolUseBlocks = lastMessage.content.filter((content) => content.type === 'tool_use');
if (toolUseBlocks.length === 0) {
return null;
}
const toolResults = await Promise.all(
toolUseBlocks.map(async (toolUse) => {
const tool = params.tools.find((t) => ('name' in t ? t.name : t.mcp_server_name) === toolUse.name);
if (!tool || !('run' in tool)) {
return {
type: 'tool_result' as const,
tool_use_id: toolUse.id,
content: `Error: Tool '${toolUse.name}' not found`,
is_error: true,
};
}
try {
let input = toolUse.input;
if ('parse' in tool && tool.parse) {
input = tool.parse(input);
}
const result = await tool.run(input);
return {
type: 'tool_result' as const,
tool_use_id: toolUse.id,
content: result,
};
} catch (error) {
return {
type: 'tool_result' as const,
tool_use_id: toolUse.id,
content:
error instanceof ToolError ?
error.content
: `Error: ${error instanceof Error ? error.message : String(error)}`,
is_error: true,
};
}
}),
);
return {
role: 'user' as const,
content: toolResults,
};
}
// vendored from typefest just to make things look a bit nicer on hover
type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {};
/**
* Parameters for creating a ToolRunner, extending MessageCreateParams with runnable tools.
*/
export type BetaToolRunnerParams = Simplify<
Omit<MessageCreateParams, 'tools'> & {
tools: (BetaToolUnion | BetaRunnableTool<any>)[];
/**
* Maximum number of iterations (API requests) to make in the tool execution loop.
* Each iteration consists of: assistant response → tool execution → tool results.
* When exceeded, the loop will terminate even if tools are still being requested.
*/
max_iterations?: number;
compactionControl?: CompactionControl;
}
>;
export type BetaToolRunnerRequestOptions = Pick<RequestOptions, 'headers'>;

View File

@@ -0,0 +1,52 @@
import { Model } from '../../resources';
export const DEFAULT_TOKEN_THRESHOLD = 100_000;
export const DEFAULT_SUMMARY_PROMPT = `You have been working on the task described above but have not yet completed it. Write a continuation summary that will allow you (or another instance of yourself) to resume work efficiently in a future context window where the conversation history will be replaced with this summary. Your summary should be structured, concise, and actionable. Include:
1. Task Overview
The user's core request and success criteria
Any clarifications or constraints they specified
2. Current State
What has been completed so far
Files created, modified, or analyzed (with paths if relevant)
Key outputs or artifacts produced
3. Important Discoveries
Technical constraints or requirements uncovered
Decisions made and their rationale
Errors encountered and how they were resolved
What approaches were tried that didn't work (and why)
4. Next Steps
Specific actions needed to complete the task
Any blockers or open questions to resolve
Priority order if multiple steps remain
5. Context to Preserve
User preferences or style requirements
Domain-specific details that aren't obvious
Any promises made to the user
Be concise but complete—err on the side of including information that would prevent duplicate work or repeated mistakes. Write in a way that enables immediate resumption of the task.
Wrap your summary in <summary></summary> tags.`;
export interface CompactionControl {
/**
* The context token threshold at which to trigger compaction.
*
* When the cumulative token count (input + output) across all messages exceeds this threshold,
* the message history will be automatically summarized and compressed.
*
* @default 100000
*/
contextTokenThreshold?: number;
/**
* The model to use for generating the compaction summary.
* If not specified, defaults to the same model used for the tool runner.
*/
model?: Model;
/**
* The prompt used to instruct the model on how to generate the summary.
*/
summaryPrompt?: string;
enabled: boolean;
}

View File

@@ -0,0 +1,47 @@
import { BetaToolResultContentBlockParam } from '../../resources/beta';
/**
* An error that can be thrown from a tool's `run` method to return structured
* content blocks as the error result, rather than just a string message.
*
* When the ToolRunner catches this error, it will use the `content` property
* as the tool result with `is_error: true`.
*
* @example
* ```ts
* const tool = {
* name: 'my_tool',
* run: async (input) => {
* if (somethingWentWrong) {
* throw new ToolError([
* { type: 'text', text: 'Error details here' },
* { type: 'image', source: { type: 'base64', data: '...', media_type: 'image/png' } },
* ]);
* }
* return 'success';
* },
* };
* ```
*/
export class ToolError extends Error {
/**
* The content to return as the tool result. This will be sent back to the model
* with `is_error: true`.
*/
readonly content: string | Array<BetaToolResultContentBlockParam>;
constructor(content: string | Array<BetaToolResultContentBlockParam>) {
const message =
typeof content === 'string' ? content : (
content
.map((block) => {
if (block.type === 'text') return block.text;
return `[${block.type}]`;
})
.join(' ')
);
super(message);
this.name = 'ToolError';
this.content = content;
}
}

View File

@@ -0,0 +1,381 @@
import { BetaRunnableTool } from './BetaRunnableTool';
import { ToolError } from './ToolError';
import { Anthropic } from '../..';
import { AnthropicError } from '../../core/error';
import { BetaMessage, BetaMessageParam, BetaToolUnion, MessageCreateParams } from '../../resources/beta';
import { BetaMessageStream } from '../BetaMessageStream';
/**
* Just Promise.withResolvers(), which is not available in all environments.
*/
function promiseWithResolvers<T>(): {
promise: Promise<T>;
resolve: (value: T) => void;
reject: (reason?: any) => void;
} {
let resolve: (value: T) => void;
let reject: (reason?: any) => void;
const promise = new Promise<T>((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve: resolve!, reject: reject! };
}
/**
* A ToolRunner handles the automatic conversation loop between the assistant and tools.
*
* A ToolRunner is an async iterable that yields either BetaMessage or BetaMessageStream objects
* depending on the streaming configuration.
*/
export class BetaToolRunner<Stream extends boolean> {
/** Whether the async iterator has been consumed */
#consumed = false;
/** Whether parameters have been mutated since the last API call */
#mutated = false;
/** Current state containing the request parameters */
#state: { params: BetaToolRunnerParams };
/** Promise for the last message received from the assistant */
#message?: Promise<BetaMessage> | undefined;
/** Cached tool response to avoid redundant executions */
#toolResponse?: Promise<BetaMessageParam | null> | undefined;
/** Promise resolvers for waiting on completion */
#completion: {
promise: Promise<BetaMessage>;
resolve: (value: BetaMessage) => void;
reject: (reason?: any) => void;
};
/** Number of iterations (API requests) made so far */
#iterationCount = 0;
constructor(
private client: Anthropic,
params: BetaToolRunnerParams,
) {
this.#state = {
params: {
// You can't clone the entire params since there are functions as handlers.
// You also don't really need to clone params.messages, but it probably will prevent a foot gun
// somewhere.
...params,
messages: structuredClone(params.messages),
},
};
this.#completion = promiseWithResolvers();
}
async *[Symbol.asyncIterator](): AsyncIterator<
Stream extends true ? BetaMessageStream
: Stream extends false ? BetaMessage
: BetaMessage | BetaMessageStream
> {
if (this.#consumed) {
throw new AnthropicError('Cannot iterate over a consumed stream');
}
this.#consumed = true;
this.#mutated = true;
this.#toolResponse = undefined;
try {
while (true) {
let stream;
try {
if (
this.#state.params.max_iterations &&
this.#iterationCount >= this.#state.params.max_iterations
) {
break;
}
this.#mutated = false;
this.#message = undefined;
this.#toolResponse = undefined;
this.#iterationCount++;
const { max_iterations, ...params } = this.#state.params;
if (params.stream) {
stream = this.client.beta.messages.stream({ ...params });
this.#message = stream.finalMessage();
// Make sure that this promise doesn't throw before we get the option to do something about it.
// Error will be caught when we call await this.#message ultimately
this.#message.catch(() => {});
yield stream as any;
} else {
this.#message = this.client.beta.messages.create({ ...params, stream: false });
yield this.#message as any;
}
if (!this.#mutated) {
const { role, content } = await this.#message;
this.#state.params.messages.push({ role, content });
}
const toolMessage = await this.#generateToolResponse(this.#state.params.messages.at(-1)!);
if (toolMessage) {
this.#state.params.messages.push(toolMessage);
}
if (!toolMessage && !this.#mutated) {
break;
}
} finally {
if (stream) {
stream.abort();
}
}
}
if (!this.#message) {
throw new AnthropicError('ToolRunner concluded without a message from the server');
}
this.#completion.resolve(await this.#message);
} catch (error) {
this.#consumed = false;
// Silence unhandled promise errors
this.#completion.promise.catch(() => {});
this.#completion.reject(error);
this.#completion = promiseWithResolvers();
throw error;
}
}
/**
* Update the parameters for the next API call. This invalidates any cached tool responses.
*
* @param paramsOrMutator - Either new parameters or a function to mutate existing parameters
*
* @example
* // Direct parameter update
* runner.setMessagesParams({
* model: 'claude-haiku-4-5',
* max_tokens: 500,
* });
*
* @example
* // Using a mutator function
* runner.setMessagesParams((params) => ({
* ...params,
* max_tokens: 100,
* }));
*/
setMessagesParams(params: BetaToolRunnerParams): void;
setMessagesParams(mutator: (prevParams: BetaToolRunnerParams) => BetaToolRunnerParams): void;
setMessagesParams(
paramsOrMutator: BetaToolRunnerParams | ((prevParams: BetaToolRunnerParams) => BetaToolRunnerParams),
) {
if (typeof paramsOrMutator === 'function') {
this.#state.params = paramsOrMutator(this.#state.params);
} else {
this.#state.params = paramsOrMutator;
}
this.#mutated = true;
// Invalidate cached tool response since parameters changed
this.#toolResponse = undefined;
}
/**
* Get the tool response for the last message from the assistant.
* Avoids redundant tool executions by caching results.
*
* @returns A promise that resolves to a BetaMessageParam containing tool results, or null if no tools need to be executed
*
* @example
* const toolResponse = await runner.generateToolResponse();
* if (toolResponse) {
* console.log('Tool results:', toolResponse.content);
* }
*/
async generateToolResponse() {
const message = (await this.#message) ?? this.params.messages.at(-1);
if (!message) {
return null;
}
return this.#generateToolResponse(message);
}
async #generateToolResponse(lastMessage: BetaMessageParam) {
if (this.#toolResponse !== undefined) {
return this.#toolResponse;
}
this.#toolResponse = generateToolResponse(this.#state.params, lastMessage);
return this.#toolResponse;
}
/**
* Wait for the async iterator to complete. This works even if the async iterator hasn't yet started, and
* will wait for an instance to start and go to completion.
*
* @returns A promise that resolves to the final BetaMessage when the iterator completes
*
* @example
* // Start consuming the iterator
* for await (const message of runner) {
* console.log('Message:', message.content);
* }
*
* // Meanwhile, wait for completion from another part of the code
* const finalMessage = await runner.done();
* console.log('Final response:', finalMessage.content);
*/
done(): Promise<BetaMessage> {
return this.#completion.promise;
}
/**
* Returns a promise indicating that the stream is done. Unlike .done(), this will eagerly read the stream:
* * If the iterator has not been consumed, consume the entire iterator and return the final message from the
* assistant.
* * If the iterator has been consumed, waits for it to complete and returns the final message.
*
* @returns A promise that resolves to the final BetaMessage from the conversation
* @throws {AnthropicError} If no messages were processed during the conversation
*
* @example
* const finalMessage = await runner.runUntilDone();
* console.log('Final response:', finalMessage.content);
*/
async runUntilDone(): Promise<BetaMessage> {
// If not yet consumed, start consuming and wait for completion
if (!this.#consumed) {
for await (const _ of this) {
// Iterator naturally populates this.#message
}
}
// If consumed but not completed, wait for completion
return this.done();
}
/**
* Get the current parameters being used by the ToolRunner.
*
* @returns A readonly view of the current ToolRunnerParams
*
* @example
* const currentParams = runner.params;
* console.log('Current model:', currentParams.model);
* console.log('Message count:', currentParams.messages.length);
*/
get params(): Readonly<BetaToolRunnerParams> {
return this.#state.params as Readonly<BetaToolRunnerParams>;
}
/**
* Add one or more messages to the conversation history.
*
* @param messages - One or more BetaMessageParam objects to add to the conversation
*
* @example
* runner.pushMessages(
* { role: 'user', content: 'Also, what about the weather in NYC?' }
* );
*
* @example
* // Adding multiple messages
* runner.pushMessages(
* { role: 'user', content: 'What about NYC?' },
* { role: 'user', content: 'And Boston?' }
* );
*/
pushMessages(...messages: BetaMessageParam[]) {
this.setMessagesParams((params) => ({
...params,
messages: [...params.messages, ...messages],
}));
}
/**
* Makes the ToolRunner directly awaitable, equivalent to calling .runUntilDone()
* This allows using `await runner` instead of `await runner.runUntilDone()`
*/
then<TResult1 = BetaMessage, TResult2 = never>(
onfulfilled?: ((value: BetaMessage) => TResult1 | PromiseLike<TResult1>) | undefined | null,
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null,
): Promise<TResult1 | TResult2> {
return this.runUntilDone().then(onfulfilled, onrejected);
}
}
async function generateToolResponse(
params: BetaToolRunnerParams,
lastMessage = params.messages.at(-1),
): Promise<BetaMessageParam | null> {
// Only process if the last message is from the assistant and has tool use blocks
if (
!lastMessage ||
lastMessage.role !== 'assistant' ||
!lastMessage.content ||
typeof lastMessage.content === 'string'
) {
return null;
}
const toolUseBlocks = lastMessage.content.filter((content) => content.type === 'tool_use');
if (toolUseBlocks.length === 0) {
return null;
}
const toolResults = await Promise.all(
toolUseBlocks.map(async (toolUse) => {
const tool = params.tools.find((t) => ('name' in t ? t.name : t.mcp_server_name) === toolUse.name);
if (!tool || !('run' in tool)) {
return {
type: 'tool_result' as const,
tool_use_id: toolUse.id,
content: `Error: Tool '${toolUse.name}' not found`,
is_error: true,
};
}
try {
let input = toolUse.input;
if ('parse' in tool && tool.parse) {
input = tool.parse(input);
}
const result = await tool.run(input);
return {
type: 'tool_result' as const,
tool_use_id: toolUse.id,
content: result,
};
} catch (error) {
return {
type: 'tool_result' as const,
tool_use_id: toolUse.id,
content:
error instanceof ToolError ?
error.content
: `Error: ${error instanceof Error ? error.message : String(error)}`,
is_error: true,
};
}
}),
);
return {
role: 'user' as const,
content: toolResults,
};
}
// vendored from typefest just to make things look a bit nicer on hover
type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {};
/**
* Parameters for creating a ToolRunner, extending MessageCreateParams with runnable tools.
*/
export type BetaToolRunnerParams = Simplify<
Omit<MessageCreateParams, 'tools'> & {
tools: (BetaToolUnion | BetaRunnableTool<any>)[];
/**
* Maximum number of iterations (API requests) to make in the tool execution loop.
* Each iteration consists of: assistant response → tool execution → tool results.
* When exceeded, the loop will terminate even if tools are still being requested.
*/
max_iterations?: number;
}
>;

View File

@@ -0,0 +1,124 @@
import { pop } from '../internal/utils';
// Supported string formats
const SUPPORTED_STRING_FORMATS = new Set([
'date-time',
'time',
'date',
'duration',
'email',
'hostname',
'uri',
'ipv4',
'ipv6',
'uuid',
]);
export type JSONSchema = Record<string, any>;
function deepClone<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj));
}
export function transformJSONSchema(jsonSchema: JSONSchema): JSONSchema {
const workingCopy = deepClone(jsonSchema);
return _transformJSONSchema(workingCopy);
}
function _transformJSONSchema(jsonSchema: JSONSchema): JSONSchema {
const strictSchema: JSONSchema = {};
const ref = pop(jsonSchema, '$ref');
if (ref !== undefined) {
strictSchema['$ref'] = ref;
return strictSchema;
}
const defs = pop(jsonSchema, '$defs');
if (defs !== undefined) {
const strictDefs: Record<string, any> = {};
strictSchema['$defs'] = strictDefs;
for (const [name, defSchema] of Object.entries(defs)) {
strictDefs[name] = _transformJSONSchema(defSchema as JSONSchema);
}
}
const type = pop(jsonSchema, 'type');
const anyOf = pop(jsonSchema, 'anyOf');
const oneOf = pop(jsonSchema, 'oneOf');
const allOf = pop(jsonSchema, 'allOf');
if (Array.isArray(anyOf)) {
strictSchema['anyOf'] = anyOf.map((variant) => _transformJSONSchema(variant as JSONSchema));
} else if (Array.isArray(oneOf)) {
strictSchema['anyOf'] = oneOf.map((variant) => _transformJSONSchema(variant as JSONSchema));
} else if (Array.isArray(allOf)) {
strictSchema['allOf'] = allOf.map((entry) => _transformJSONSchema(entry as JSONSchema));
} else {
if (type === undefined) {
throw new Error('JSON schema must have a type defined if anyOf/oneOf/allOf are not used');
}
strictSchema['type'] = type;
}
const description = pop(jsonSchema, 'description');
if (description !== undefined) {
strictSchema['description'] = description;
}
const title = pop(jsonSchema, 'title');
if (title !== undefined) {
strictSchema['title'] = title;
}
if (type === 'object') {
const properties = pop(jsonSchema, 'properties') || {};
strictSchema['properties'] = Object.fromEntries(
Object.entries(properties).map(([key, propSchema]) => [
key,
_transformJSONSchema(propSchema as JSONSchema),
]),
);
pop(jsonSchema, 'additionalProperties');
strictSchema['additionalProperties'] = false;
const required = pop(jsonSchema, 'required');
if (required !== undefined) {
strictSchema['required'] = required;
}
} else if (type === 'string') {
const format = pop(jsonSchema, 'format');
if (format !== undefined && SUPPORTED_STRING_FORMATS.has(format)) {
strictSchema['format'] = format;
} else if (format !== undefined) {
jsonSchema['format'] = format;
}
} else if (type === 'array') {
const items = pop(jsonSchema, 'items');
if (items !== undefined) {
strictSchema['items'] = _transformJSONSchema(items as JSONSchema);
}
const minItems = pop(jsonSchema, 'minItems');
if (minItems !== undefined && (minItems === 0 || minItems === 1)) {
strictSchema['minItems'] = minItems;
} else if (minItems !== undefined) {
jsonSchema['minItems'] = minItems;
}
}
if (Object.keys(jsonSchema).length > 0) {
const existingDescription = strictSchema['description'];
strictSchema['description'] =
(existingDescription ? existingDescription + '\n\n' : '') +
'{' +
Object.entries(jsonSchema)
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
.join(', ') +
'}';
}
return strictSchema;
}

View File

@@ -0,0 +1,2 @@
/** @deprecated Import from ./core/pagination instead */
export * from './core/pagination';

View File

@@ -0,0 +1,2 @@
/** @deprecated Import from ./core/resource instead */
export * from './core/resource';

View File

@@ -0,0 +1 @@
export * from './resources/index';

View File

@@ -0,0 +1,3 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export * from './beta/index';

View File

@@ -0,0 +1,601 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from '../../core/resource';
import * as FilesAPI from './files';
import {
DeletedFile,
FileDeleteParams,
FileDownloadParams,
FileListParams,
FileMetadata,
FileMetadataPage,
FileRetrieveMetadataParams,
FileUploadParams,
Files,
} from './files';
import * as ModelsAPI from './models';
import {
BetaCapabilitySupport,
BetaContextManagementCapability,
BetaEffortCapability,
BetaModelCapabilities,
BetaModelInfo,
BetaModelInfosPage,
BetaThinkingCapability,
BetaThinkingTypes,
ModelListParams,
ModelRetrieveParams,
Models,
} from './models';
import * as MessagesAPI from './messages/messages';
import {
BetaAllThinkingTurns,
BetaBase64ImageSource,
BetaBase64PDFBlock,
BetaBase64PDFSource,
BetaBashCodeExecutionOutputBlock,
BetaBashCodeExecutionOutputBlockParam,
BetaBashCodeExecutionResultBlock,
BetaBashCodeExecutionResultBlockParam,
BetaBashCodeExecutionToolResultBlock,
BetaBashCodeExecutionToolResultBlockParam,
BetaBashCodeExecutionToolResultError,
BetaBashCodeExecutionToolResultErrorParam,
BetaCacheControlEphemeral,
BetaCacheCreation,
BetaCitationCharLocation,
BetaCitationCharLocationParam,
BetaCitationConfig,
BetaCitationContentBlockLocation,
BetaCitationContentBlockLocationParam,
BetaCitationPageLocation,
BetaCitationPageLocationParam,
BetaCitationSearchResultLocation,
BetaCitationSearchResultLocationParam,
BetaCitationWebSearchResultLocationParam,
BetaCitationsConfigParam,
BetaCitationsDelta,
BetaCitationsWebSearchResultLocation,
BetaClearThinking20251015Edit,
BetaClearThinking20251015EditResponse,
BetaClearToolUses20250919Edit,
BetaClearToolUses20250919EditResponse,
BetaCodeExecutionOutputBlock,
BetaCodeExecutionOutputBlockParam,
BetaCodeExecutionResultBlock,
BetaCodeExecutionResultBlockParam,
BetaCodeExecutionTool20250522,
BetaCodeExecutionTool20250825,
BetaCodeExecutionTool20260120,
BetaCodeExecutionToolResultBlock,
BetaCodeExecutionToolResultBlockContent,
BetaCodeExecutionToolResultBlockParam,
BetaCodeExecutionToolResultBlockParamContent,
BetaCodeExecutionToolResultError,
BetaCodeExecutionToolResultErrorCode,
BetaCodeExecutionToolResultErrorParam,
BetaCompact20260112Edit,
BetaCompactionBlock,
BetaCompactionBlockParam,
BetaCompactionContentBlockDelta,
BetaCompactionIterationUsage,
BetaContainer,
BetaContainerParams,
BetaContainerUploadBlock,
BetaContainerUploadBlockParam,
BetaContentBlock,
BetaContentBlockParam,
BetaContentBlockSource,
BetaContentBlockSourceContent,
BetaContextManagementConfig,
BetaContextManagementResponse,
BetaCountTokensContextManagementResponse,
BetaDirectCaller,
BetaDocumentBlock,
BetaEncryptedCodeExecutionResultBlock,
BetaEncryptedCodeExecutionResultBlockParam,
BetaFileDocumentSource,
BetaFileImageSource,
BetaImageBlockParam,
BetaInputJSONDelta,
BetaJSONOutputFormat,
BetaInputTokensClearAtLeast,
BetaInputTokensTrigger,
BetaMCPToolResultBlock,
BetaMCPToolUseBlock,
BetaMCPToolUseBlockParam,
BetaMCPToolset,
BetaMemoryTool20250818,
BetaMemoryTool20250818Command,
BetaMemoryTool20250818CreateCommand,
BetaMemoryTool20250818DeleteCommand,
BetaMemoryTool20250818InsertCommand,
BetaMemoryTool20250818RenameCommand,
BetaMemoryTool20250818StrReplaceCommand,
BetaMemoryTool20250818ViewCommand,
BetaMessage,
BetaMessageDeltaUsage,
BetaMessageIterationUsage,
BetaMessageParam,
BetaMessageTokensCount,
BetaMetadata,
BetaOutputConfig,
BetaPlainTextSource,
BetaRawContentBlockDelta,
BetaRawContentBlockDeltaEvent,
BetaRawContentBlockStartEvent,
BetaRawContentBlockStopEvent,
BetaRawMessageDeltaEvent,
BetaRawMessageStartEvent,
BetaRawMessageStopEvent,
BetaRawMessageStreamEvent,
BetaRedactedThinkingBlock,
BetaRedactedThinkingBlockParam,
BetaRequestDocumentBlock,
BetaRequestMCPServerToolConfiguration,
BetaRequestMCPServerURLDefinition,
BetaRequestMCPToolResultBlockParam,
BetaSearchResultBlockParam,
BetaServerToolCaller,
BetaServerToolCaller20260120,
BetaServerToolUsage,
BetaServerToolUseBlock,
BetaServerToolUseBlockParam,
BetaSignatureDelta,
BetaSkill,
BetaSkillParams,
BetaStopReason,
BetaTextBlock,
BetaTextBlockParam,
BetaTextCitation,
BetaTextCitationParam,
BetaTextDelta,
BetaTextEditorCodeExecutionCreateResultBlock,
BetaTextEditorCodeExecutionCreateResultBlockParam,
BetaTextEditorCodeExecutionStrReplaceResultBlock,
BetaTextEditorCodeExecutionStrReplaceResultBlockParam,
BetaTextEditorCodeExecutionToolResultBlock,
BetaTextEditorCodeExecutionToolResultBlockParam,
BetaTextEditorCodeExecutionToolResultError,
BetaTextEditorCodeExecutionToolResultErrorParam,
BetaTextEditorCodeExecutionViewResultBlock,
BetaTextEditorCodeExecutionViewResultBlockParam,
BetaThinkingBlock,
BetaThinkingBlockParam,
BetaThinkingConfigAdaptive,
BetaThinkingConfigDisabled,
BetaThinkingConfigEnabled,
BetaThinkingConfigParam,
BetaThinkingDelta,
BetaThinkingTurns,
BetaTool,
BetaToolBash20241022,
BetaToolBash20250124,
BetaToolChoice,
BetaToolChoiceAny,
BetaToolChoiceAuto,
BetaToolChoiceNone,
BetaToolChoiceTool,
BetaToolComputerUse20241022,
BetaToolComputerUse20250124,
BetaToolComputerUse20251124,
BetaToolReferenceBlock,
BetaToolReferenceBlockParam,
BetaToolResultBlockParam,
BetaToolTextEditor20241022,
BetaToolTextEditor20250124,
BetaToolTextEditor20250429,
BetaToolTextEditor20250728,
BetaToolUnion,
BetaToolUseBlock,
BetaToolUseBlockParam,
BetaToolUsesKeep,
BetaToolUsesTrigger,
BetaURLImageSource,
BetaURLPDFSource,
BetaUsage,
BetaUserLocation,
BetaWebFetchBlock,
BetaWebFetchBlockParam,
BetaWebFetchTool20250910,
BetaWebFetchTool20260209,
BetaWebFetchTool20260309,
BetaWebFetchToolResultBlock,
BetaWebFetchToolResultBlockParam,
BetaWebFetchToolResultErrorBlock,
BetaWebFetchToolResultErrorBlockParam,
BetaWebFetchToolResultErrorCode,
BetaWebSearchResultBlock,
BetaWebSearchResultBlockParam,
BetaWebSearchTool20250305,
BetaWebSearchTool20260209,
BetaWebSearchToolRequestError,
BetaWebSearchToolResultBlock,
BetaWebSearchToolResultBlockContent,
BetaWebSearchToolResultBlockParam,
BetaWebSearchToolResultBlockParamContent,
BetaWebSearchToolResultError,
BetaWebSearchToolResultErrorCode,
MessageCountTokensParams,
MessageCreateParams,
MessageCreateParamsNonStreaming,
MessageCreateParamsStreaming,
Messages,
BetaMCPToolConfig,
BetaMCPToolDefaultConfig,
} from './messages/messages';
import * as SkillsAPI from './skills/skills';
import {
SkillCreateParams,
SkillCreateResponse,
SkillDeleteParams,
SkillDeleteResponse,
SkillListParams,
SkillListResponse,
SkillListResponsesPageCursor,
SkillRetrieveParams,
SkillRetrieveResponse,
Skills,
} from './skills/skills';
export class Beta extends APIResource {
models: ModelsAPI.Models = new ModelsAPI.Models(this._client);
messages: MessagesAPI.Messages = new MessagesAPI.Messages(this._client);
files: FilesAPI.Files = new FilesAPI.Files(this._client);
skills: SkillsAPI.Skills = new SkillsAPI.Skills(this._client);
}
export type AnthropicBeta =
| (string & {})
| 'message-batches-2024-09-24'
| 'prompt-caching-2024-07-31'
| 'computer-use-2024-10-22'
| 'computer-use-2025-01-24'
| 'pdfs-2024-09-25'
| 'token-counting-2024-11-01'
| 'token-efficient-tools-2025-02-19'
| 'output-128k-2025-02-19'
| 'files-api-2025-04-14'
| 'mcp-client-2025-04-04'
| 'mcp-client-2025-11-20'
| 'dev-full-thinking-2025-05-14'
| 'interleaved-thinking-2025-05-14'
| 'code-execution-2025-05-22'
| 'extended-cache-ttl-2025-04-11'
| 'context-1m-2025-08-07'
| 'context-management-2025-06-27'
| 'model-context-window-exceeded-2025-08-26'
| 'skills-2025-10-02'
| 'fast-mode-2026-02-01'
| 'output-300k-2026-03-24';
export interface BetaAPIError {
message: string;
type: 'api_error';
}
export interface BetaAuthenticationError {
message: string;
type: 'authentication_error';
}
export interface BetaBillingError {
message: string;
type: 'billing_error';
}
export type BetaError =
| BetaInvalidRequestError
| BetaAuthenticationError
| BetaBillingError
| BetaPermissionError
| BetaNotFoundError
| BetaRateLimitError
| BetaGatewayTimeoutError
| BetaAPIError
| BetaOverloadedError;
export interface BetaErrorResponse {
error: BetaError;
request_id: string | null;
type: 'error';
}
export interface BetaGatewayTimeoutError {
message: string;
type: 'timeout_error';
}
export interface BetaInvalidRequestError {
message: string;
type: 'invalid_request_error';
}
export interface BetaNotFoundError {
message: string;
type: 'not_found_error';
}
export interface BetaOverloadedError {
message: string;
type: 'overloaded_error';
}
export interface BetaPermissionError {
message: string;
type: 'permission_error';
}
export interface BetaRateLimitError {
message: string;
type: 'rate_limit_error';
}
Beta.Models = Models;
Beta.Messages = Messages;
Beta.Files = Files;
Beta.Skills = Skills;
export declare namespace Beta {
export {
type AnthropicBeta as AnthropicBeta,
type BetaAPIError as BetaAPIError,
type BetaAuthenticationError as BetaAuthenticationError,
type BetaBillingError as BetaBillingError,
type BetaError as BetaError,
type BetaErrorResponse as BetaErrorResponse,
type BetaGatewayTimeoutError as BetaGatewayTimeoutError,
type BetaInvalidRequestError as BetaInvalidRequestError,
type BetaNotFoundError as BetaNotFoundError,
type BetaOverloadedError as BetaOverloadedError,
type BetaPermissionError as BetaPermissionError,
type BetaRateLimitError as BetaRateLimitError,
};
export {
Models as Models,
type BetaCapabilitySupport as BetaCapabilitySupport,
type BetaContextManagementCapability as BetaContextManagementCapability,
type BetaEffortCapability as BetaEffortCapability,
type BetaModelCapabilities as BetaModelCapabilities,
type BetaModelInfo as BetaModelInfo,
type BetaThinkingCapability as BetaThinkingCapability,
type BetaThinkingTypes as BetaThinkingTypes,
type BetaModelInfosPage as BetaModelInfosPage,
type ModelRetrieveParams as ModelRetrieveParams,
type ModelListParams as ModelListParams,
};
export {
Messages as Messages,
type BetaAllThinkingTurns as BetaAllThinkingTurns,
type BetaBase64ImageSource as BetaBase64ImageSource,
type BetaBase64PDFSource as BetaBase64PDFSource,
type BetaBashCodeExecutionOutputBlock as BetaBashCodeExecutionOutputBlock,
type BetaBashCodeExecutionOutputBlockParam as BetaBashCodeExecutionOutputBlockParam,
type BetaBashCodeExecutionResultBlock as BetaBashCodeExecutionResultBlock,
type BetaBashCodeExecutionResultBlockParam as BetaBashCodeExecutionResultBlockParam,
type BetaBashCodeExecutionToolResultBlock as BetaBashCodeExecutionToolResultBlock,
type BetaBashCodeExecutionToolResultBlockParam as BetaBashCodeExecutionToolResultBlockParam,
type BetaBashCodeExecutionToolResultError as BetaBashCodeExecutionToolResultError,
type BetaBashCodeExecutionToolResultErrorParam as BetaBashCodeExecutionToolResultErrorParam,
type BetaCacheControlEphemeral as BetaCacheControlEphemeral,
type BetaCacheCreation as BetaCacheCreation,
type BetaCitationCharLocation as BetaCitationCharLocation,
type BetaCitationCharLocationParam as BetaCitationCharLocationParam,
type BetaCitationConfig as BetaCitationConfig,
type BetaCitationContentBlockLocation as BetaCitationContentBlockLocation,
type BetaCitationContentBlockLocationParam as BetaCitationContentBlockLocationParam,
type BetaCitationPageLocation as BetaCitationPageLocation,
type BetaCitationPageLocationParam as BetaCitationPageLocationParam,
type BetaCitationSearchResultLocation as BetaCitationSearchResultLocation,
type BetaCitationSearchResultLocationParam as BetaCitationSearchResultLocationParam,
type BetaCitationWebSearchResultLocationParam as BetaCitationWebSearchResultLocationParam,
type BetaCitationsConfigParam as BetaCitationsConfigParam,
type BetaCitationsDelta as BetaCitationsDelta,
type BetaCitationsWebSearchResultLocation as BetaCitationsWebSearchResultLocation,
type BetaClearThinking20251015Edit as BetaClearThinking20251015Edit,
type BetaClearThinking20251015EditResponse as BetaClearThinking20251015EditResponse,
type BetaClearToolUses20250919Edit as BetaClearToolUses20250919Edit,
type BetaClearToolUses20250919EditResponse as BetaClearToolUses20250919EditResponse,
type BetaCodeExecutionOutputBlock as BetaCodeExecutionOutputBlock,
type BetaCodeExecutionOutputBlockParam as BetaCodeExecutionOutputBlockParam,
type BetaCodeExecutionResultBlock as BetaCodeExecutionResultBlock,
type BetaCodeExecutionResultBlockParam as BetaCodeExecutionResultBlockParam,
type BetaCodeExecutionTool20250522 as BetaCodeExecutionTool20250522,
type BetaCodeExecutionTool20250825 as BetaCodeExecutionTool20250825,
type BetaCodeExecutionTool20260120 as BetaCodeExecutionTool20260120,
type BetaCodeExecutionToolResultBlock as BetaCodeExecutionToolResultBlock,
type BetaCodeExecutionToolResultBlockContent as BetaCodeExecutionToolResultBlockContent,
type BetaCodeExecutionToolResultBlockParam as BetaCodeExecutionToolResultBlockParam,
type BetaCodeExecutionToolResultBlockParamContent as BetaCodeExecutionToolResultBlockParamContent,
type BetaCodeExecutionToolResultError as BetaCodeExecutionToolResultError,
type BetaCodeExecutionToolResultErrorCode as BetaCodeExecutionToolResultErrorCode,
type BetaCodeExecutionToolResultErrorParam as BetaCodeExecutionToolResultErrorParam,
type BetaCompact20260112Edit as BetaCompact20260112Edit,
type BetaCompactionBlock as BetaCompactionBlock,
type BetaCompactionBlockParam as BetaCompactionBlockParam,
type BetaCompactionContentBlockDelta as BetaCompactionContentBlockDelta,
type BetaCompactionIterationUsage as BetaCompactionIterationUsage,
type BetaContainer as BetaContainer,
type BetaContainerParams as BetaContainerParams,
type BetaContainerUploadBlock as BetaContainerUploadBlock,
type BetaContainerUploadBlockParam as BetaContainerUploadBlockParam,
type BetaContentBlock as BetaContentBlock,
type BetaContentBlockParam as BetaContentBlockParam,
type BetaContentBlockSource as BetaContentBlockSource,
type BetaContentBlockSourceContent as BetaContentBlockSourceContent,
type BetaContextManagementConfig as BetaContextManagementConfig,
type BetaContextManagementResponse as BetaContextManagementResponse,
type BetaCountTokensContextManagementResponse as BetaCountTokensContextManagementResponse,
type BetaDirectCaller as BetaDirectCaller,
type BetaDocumentBlock as BetaDocumentBlock,
type BetaEncryptedCodeExecutionResultBlock as BetaEncryptedCodeExecutionResultBlock,
type BetaEncryptedCodeExecutionResultBlockParam as BetaEncryptedCodeExecutionResultBlockParam,
type BetaFileDocumentSource as BetaFileDocumentSource,
type BetaFileImageSource as BetaFileImageSource,
type BetaImageBlockParam as BetaImageBlockParam,
type BetaInputJSONDelta as BetaInputJSONDelta,
type BetaJSONOutputFormat as BetaJSONOutputFormat,
type BetaInputTokensClearAtLeast as BetaInputTokensClearAtLeast,
type BetaInputTokensTrigger as BetaInputTokensTrigger,
type BetaMCPToolConfig as BetaMCPToolConfig,
type BetaMCPToolDefaultConfig as BetaMCPToolDefaultConfig,
type BetaMCPToolResultBlock as BetaMCPToolResultBlock,
type BetaMCPToolUseBlock as BetaMCPToolUseBlock,
type BetaMCPToolUseBlockParam as BetaMCPToolUseBlockParam,
type BetaMCPToolset as BetaMCPToolset,
type BetaMemoryTool20250818 as BetaMemoryTool20250818,
type BetaMemoryTool20250818Command as BetaMemoryTool20250818Command,
type BetaMemoryTool20250818CreateCommand as BetaMemoryTool20250818CreateCommand,
type BetaMemoryTool20250818DeleteCommand as BetaMemoryTool20250818DeleteCommand,
type BetaMemoryTool20250818InsertCommand as BetaMemoryTool20250818InsertCommand,
type BetaMemoryTool20250818RenameCommand as BetaMemoryTool20250818RenameCommand,
type BetaMemoryTool20250818StrReplaceCommand as BetaMemoryTool20250818StrReplaceCommand,
type BetaMemoryTool20250818ViewCommand as BetaMemoryTool20250818ViewCommand,
type BetaMessage as BetaMessage,
type BetaMessageDeltaUsage as BetaMessageDeltaUsage,
type BetaMessageIterationUsage as BetaMessageIterationUsage,
type BetaMessageParam as BetaMessageParam,
type BetaMessageTokensCount as BetaMessageTokensCount,
type BetaMetadata as BetaMetadata,
type BetaOutputConfig as BetaOutputConfig,
type BetaPlainTextSource as BetaPlainTextSource,
type BetaRawContentBlockDelta as BetaRawContentBlockDelta,
type BetaRawContentBlockDeltaEvent as BetaRawContentBlockDeltaEvent,
type BetaRawContentBlockStartEvent as BetaRawContentBlockStartEvent,
type BetaRawContentBlockStopEvent as BetaRawContentBlockStopEvent,
type BetaRawMessageDeltaEvent as BetaRawMessageDeltaEvent,
type BetaRawMessageStartEvent as BetaRawMessageStartEvent,
type BetaRawMessageStopEvent as BetaRawMessageStopEvent,
type BetaRawMessageStreamEvent as BetaRawMessageStreamEvent,
type BetaRedactedThinkingBlock as BetaRedactedThinkingBlock,
type BetaRedactedThinkingBlockParam as BetaRedactedThinkingBlockParam,
type BetaRequestDocumentBlock as BetaRequestDocumentBlock,
type BetaRequestMCPServerToolConfiguration as BetaRequestMCPServerToolConfiguration,
type BetaRequestMCPServerURLDefinition as BetaRequestMCPServerURLDefinition,
type BetaRequestMCPToolResultBlockParam as BetaRequestMCPToolResultBlockParam,
type BetaSearchResultBlockParam as BetaSearchResultBlockParam,
type BetaServerToolCaller as BetaServerToolCaller,
type BetaServerToolCaller20260120 as BetaServerToolCaller20260120,
type BetaServerToolUsage as BetaServerToolUsage,
type BetaServerToolUseBlock as BetaServerToolUseBlock,
type BetaServerToolUseBlockParam as BetaServerToolUseBlockParam,
type BetaSignatureDelta as BetaSignatureDelta,
type BetaSkill as BetaSkill,
type BetaSkillParams as BetaSkillParams,
type BetaStopReason as BetaStopReason,
type BetaTextBlock as BetaTextBlock,
type BetaTextBlockParam as BetaTextBlockParam,
type BetaTextCitation as BetaTextCitation,
type BetaTextCitationParam as BetaTextCitationParam,
type BetaTextDelta as BetaTextDelta,
type BetaTextEditorCodeExecutionCreateResultBlock as BetaTextEditorCodeExecutionCreateResultBlock,
type BetaTextEditorCodeExecutionCreateResultBlockParam as BetaTextEditorCodeExecutionCreateResultBlockParam,
type BetaTextEditorCodeExecutionStrReplaceResultBlock as BetaTextEditorCodeExecutionStrReplaceResultBlock,
type BetaTextEditorCodeExecutionStrReplaceResultBlockParam as BetaTextEditorCodeExecutionStrReplaceResultBlockParam,
type BetaTextEditorCodeExecutionToolResultBlock as BetaTextEditorCodeExecutionToolResultBlock,
type BetaTextEditorCodeExecutionToolResultBlockParam as BetaTextEditorCodeExecutionToolResultBlockParam,
type BetaTextEditorCodeExecutionToolResultError as BetaTextEditorCodeExecutionToolResultError,
type BetaTextEditorCodeExecutionToolResultErrorParam as BetaTextEditorCodeExecutionToolResultErrorParam,
type BetaTextEditorCodeExecutionViewResultBlock as BetaTextEditorCodeExecutionViewResultBlock,
type BetaTextEditorCodeExecutionViewResultBlockParam as BetaTextEditorCodeExecutionViewResultBlockParam,
type BetaThinkingBlock as BetaThinkingBlock,
type BetaThinkingBlockParam as BetaThinkingBlockParam,
type BetaThinkingConfigAdaptive as BetaThinkingConfigAdaptive,
type BetaThinkingConfigDisabled as BetaThinkingConfigDisabled,
type BetaThinkingConfigEnabled as BetaThinkingConfigEnabled,
type BetaThinkingConfigParam as BetaThinkingConfigParam,
type BetaThinkingDelta as BetaThinkingDelta,
type BetaThinkingTurns as BetaThinkingTurns,
type BetaTool as BetaTool,
type BetaToolBash20241022 as BetaToolBash20241022,
type BetaToolBash20250124 as BetaToolBash20250124,
type BetaToolChoice as BetaToolChoice,
type BetaToolChoiceAny as BetaToolChoiceAny,
type BetaToolChoiceAuto as BetaToolChoiceAuto,
type BetaToolChoiceNone as BetaToolChoiceNone,
type BetaToolChoiceTool as BetaToolChoiceTool,
type BetaToolComputerUse20241022 as BetaToolComputerUse20241022,
type BetaToolComputerUse20250124 as BetaToolComputerUse20250124,
type BetaToolComputerUse20251124 as BetaToolComputerUse20251124,
type BetaToolReferenceBlock as BetaToolReferenceBlock,
type BetaToolReferenceBlockParam as BetaToolReferenceBlockParam,
type BetaToolResultBlockParam as BetaToolResultBlockParam,
type BetaToolTextEditor20241022 as BetaToolTextEditor20241022,
type BetaToolTextEditor20250124 as BetaToolTextEditor20250124,
type BetaToolTextEditor20250429 as BetaToolTextEditor20250429,
type BetaToolTextEditor20250728 as BetaToolTextEditor20250728,
type BetaToolUnion as BetaToolUnion,
type BetaToolUseBlock as BetaToolUseBlock,
type BetaToolUseBlockParam as BetaToolUseBlockParam,
type BetaToolUsesKeep as BetaToolUsesKeep,
type BetaToolUsesTrigger as BetaToolUsesTrigger,
type BetaURLImageSource as BetaURLImageSource,
type BetaURLPDFSource as BetaURLPDFSource,
type BetaUsage as BetaUsage,
type BetaUserLocation as BetaUserLocation,
type BetaWebFetchBlock as BetaWebFetchBlock,
type BetaWebFetchBlockParam as BetaWebFetchBlockParam,
type BetaWebFetchTool20250910 as BetaWebFetchTool20250910,
type BetaWebFetchTool20260209 as BetaWebFetchTool20260209,
type BetaWebFetchTool20260309 as BetaWebFetchTool20260309,
type BetaWebFetchToolResultBlock as BetaWebFetchToolResultBlock,
type BetaWebFetchToolResultBlockParam as BetaWebFetchToolResultBlockParam,
type BetaWebFetchToolResultErrorBlock as BetaWebFetchToolResultErrorBlock,
type BetaWebFetchToolResultErrorBlockParam as BetaWebFetchToolResultErrorBlockParam,
type BetaWebFetchToolResultErrorCode as BetaWebFetchToolResultErrorCode,
type BetaWebSearchResultBlock as BetaWebSearchResultBlock,
type BetaWebSearchResultBlockParam as BetaWebSearchResultBlockParam,
type BetaWebSearchTool20250305 as BetaWebSearchTool20250305,
type BetaWebSearchTool20260209 as BetaWebSearchTool20260209,
type BetaWebSearchToolRequestError as BetaWebSearchToolRequestError,
type BetaWebSearchToolResultBlock as BetaWebSearchToolResultBlock,
type BetaWebSearchToolResultBlockContent as BetaWebSearchToolResultBlockContent,
type BetaWebSearchToolResultBlockParam as BetaWebSearchToolResultBlockParam,
type BetaWebSearchToolResultBlockParamContent as BetaWebSearchToolResultBlockParamContent,
type BetaWebSearchToolResultError as BetaWebSearchToolResultError,
type BetaWebSearchToolResultErrorCode as BetaWebSearchToolResultErrorCode,
type BetaBase64PDFBlock as BetaBase64PDFBlock,
type MessageCreateParams as MessageCreateParams,
type MessageCreateParamsNonStreaming as MessageCreateParamsNonStreaming,
type MessageCreateParamsStreaming as MessageCreateParamsStreaming,
type MessageCountTokensParams as MessageCountTokensParams,
};
export {
Files as Files,
type DeletedFile as DeletedFile,
type FileMetadata as FileMetadata,
type FileMetadataPage as FileMetadataPage,
type FileListParams as FileListParams,
type FileDeleteParams as FileDeleteParams,
type FileDownloadParams as FileDownloadParams,
type FileRetrieveMetadataParams as FileRetrieveMetadataParams,
type FileUploadParams as FileUploadParams,
};
export {
Skills as Skills,
type SkillCreateResponse as SkillCreateResponse,
type SkillRetrieveResponse as SkillRetrieveResponse,
type SkillListResponse as SkillListResponse,
type SkillDeleteResponse as SkillDeleteResponse,
type SkillListResponsesPageCursor as SkillListResponsesPageCursor,
type SkillCreateParams as SkillCreateParams,
type SkillRetrieveParams as SkillRetrieveParams,
type SkillListParams as SkillListParams,
type SkillDeleteParams as SkillDeleteParams,
};
}

View File

@@ -0,0 +1,261 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from '../../core/resource';
import * as BetaAPI from './beta';
import { APIPromise } from '../../core/api-promise';
import { Page, type PageParams, PagePromise } from '../../core/pagination';
import { type Uploadable } from '../../core/uploads';
import { buildHeaders } from '../../internal/headers';
import { RequestOptions } from '../../internal/request-options';
import { stainlessHelperHeaderFromFile } from '../../lib/stainless-helper-header';
import { multipartFormRequestOptions } from '../../internal/uploads';
import { path } from '../../internal/utils/path';
export class Files extends APIResource {
/**
* List Files
*
* @example
* ```ts
* // Automatically fetches more pages as needed.
* for await (const fileMetadata of client.beta.files.list()) {
* // ...
* }
* ```
*/
list(
params: FileListParams | null | undefined = {},
options?: RequestOptions,
): PagePromise<FileMetadataPage, FileMetadata> {
const { betas, ...query } = params ?? {};
return this._client.getAPIList('/v1/files', Page<FileMetadata>, {
query,
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'files-api-2025-04-14'].toString() },
options?.headers,
]),
});
}
/**
* Delete File
*
* @example
* ```ts
* const deletedFile = await client.beta.files.delete(
* 'file_id',
* );
* ```
*/
delete(
fileID: string,
params: FileDeleteParams | null | undefined = {},
options?: RequestOptions,
): APIPromise<DeletedFile> {
const { betas } = params ?? {};
return this._client.delete(path`/v1/files/${fileID}`, {
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'files-api-2025-04-14'].toString() },
options?.headers,
]),
});
}
/**
* Download File
*
* @example
* ```ts
* const response = await client.beta.files.download(
* 'file_id',
* );
*
* const content = await response.blob();
* console.log(content);
* ```
*/
download(
fileID: string,
params: FileDownloadParams | null | undefined = {},
options?: RequestOptions,
): APIPromise<Response> {
const { betas } = params ?? {};
return this._client.get(path`/v1/files/${fileID}/content`, {
...options,
headers: buildHeaders([
{
'anthropic-beta': [...(betas ?? []), 'files-api-2025-04-14'].toString(),
Accept: 'application/binary',
},
options?.headers,
]),
__binaryResponse: true,
});
}
/**
* Get File Metadata
*
* @example
* ```ts
* const fileMetadata =
* await client.beta.files.retrieveMetadata('file_id');
* ```
*/
retrieveMetadata(
fileID: string,
params: FileRetrieveMetadataParams | null | undefined = {},
options?: RequestOptions,
): APIPromise<FileMetadata> {
const { betas } = params ?? {};
return this._client.get(path`/v1/files/${fileID}`, {
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'files-api-2025-04-14'].toString() },
options?.headers,
]),
});
}
/**
* Upload File
*
* @example
* ```ts
* const fileMetadata = await client.beta.files.upload({
* file: fs.createReadStream('path/to/file'),
* });
* ```
*/
upload(params: FileUploadParams, options?: RequestOptions): APIPromise<FileMetadata> {
const { betas, ...body } = params;
return this._client.post(
'/v1/files',
multipartFormRequestOptions(
{
body,
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'files-api-2025-04-14'].toString() },
stainlessHelperHeaderFromFile(body.file),
options?.headers,
]),
},
this._client,
),
);
}
}
export type FileMetadataPage = Page<FileMetadata>;
export interface DeletedFile {
/**
* ID of the deleted file.
*/
id: string;
/**
* Deleted object type.
*
* For file deletion, this is always `"file_deleted"`.
*/
type?: 'file_deleted';
}
export interface FileMetadata {
/**
* Unique object identifier.
*
* The format and length of IDs may change over time.
*/
id: string;
/**
* RFC 3339 datetime string representing when the file was created.
*/
created_at: string;
/**
* Original filename of the uploaded file.
*/
filename: string;
/**
* MIME type of the file.
*/
mime_type: string;
/**
* Size of the file in bytes.
*/
size_bytes: number;
/**
* Object type.
*
* For files, this is always `"file"`.
*/
type: 'file';
/**
* Whether the file can be downloaded.
*/
downloadable?: boolean;
}
export interface FileListParams extends PageParams {
/**
* Header param: Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export interface FileDeleteParams {
/**
* Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export interface FileDownloadParams {
/**
* Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export interface FileRetrieveMetadataParams {
/**
* Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export interface FileUploadParams {
/**
* Body param: The file to upload
*/
file: Uploadable;
/**
* Header param: Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export declare namespace Files {
export {
type DeletedFile as DeletedFile,
type FileMetadata as FileMetadata,
type FileMetadataPage as FileMetadataPage,
type FileListParams as FileListParams,
type FileDeleteParams as FileDeleteParams,
type FileDownloadParams as FileDownloadParams,
type FileRetrieveMetadataParams as FileRetrieveMetadataParams,
type FileUploadParams as FileUploadParams,
};
}

View File

@@ -0,0 +1,258 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export {
Beta,
type AnthropicBeta,
type BetaAPIError,
type BetaAuthenticationError,
type BetaBillingError,
type BetaError,
type BetaErrorResponse,
type BetaGatewayTimeoutError,
type BetaInvalidRequestError,
type BetaNotFoundError,
type BetaOverloadedError,
type BetaPermissionError,
type BetaRateLimitError,
} from './beta';
export {
Files,
type DeletedFile,
type FileMetadata,
type FileListParams,
type FileDeleteParams,
type FileDownloadParams,
type FileRetrieveMetadataParams,
type FileUploadParams,
type FileMetadataPage,
} from './files';
export {
Messages,
type BetaAllThinkingTurns,
type BetaBase64ImageSource,
type BetaBase64PDFSource,
type BetaBashCodeExecutionOutputBlock,
type BetaBashCodeExecutionOutputBlockParam,
type BetaBashCodeExecutionResultBlock,
type BetaBashCodeExecutionResultBlockParam,
type BetaBashCodeExecutionToolResultBlock,
type BetaBashCodeExecutionToolResultBlockParam,
type BetaBashCodeExecutionToolResultError,
type BetaBashCodeExecutionToolResultErrorParam,
type BetaCacheControlEphemeral,
type BetaCacheCreation,
type BetaCitationCharLocation,
type BetaCitationCharLocationParam,
type BetaCitationConfig,
type BetaCitationContentBlockLocation,
type BetaCitationContentBlockLocationParam,
type BetaCitationPageLocation,
type BetaCitationPageLocationParam,
type BetaCitationSearchResultLocation,
type BetaCitationSearchResultLocationParam,
type BetaCitationWebSearchResultLocationParam,
type BetaCitationsConfigParam,
type BetaCitationsDelta,
type BetaCitationsWebSearchResultLocation,
type BetaClearThinking20251015Edit,
type BetaClearThinking20251015EditResponse,
type BetaClearToolUses20250919Edit,
type BetaClearToolUses20250919EditResponse,
type BetaCodeExecutionOutputBlock,
type BetaCodeExecutionOutputBlockParam,
type BetaCodeExecutionResultBlock,
type BetaCodeExecutionResultBlockParam,
type BetaCodeExecutionTool20250522,
type BetaCodeExecutionTool20250825,
type BetaCodeExecutionTool20260120,
type BetaCodeExecutionToolResultBlock,
type BetaCodeExecutionToolResultBlockContent,
type BetaCodeExecutionToolResultBlockParam,
type BetaCodeExecutionToolResultBlockParamContent,
type BetaCodeExecutionToolResultError,
type BetaCodeExecutionToolResultErrorCode,
type BetaCodeExecutionToolResultErrorParam,
type BetaCompact20260112Edit,
type BetaCompactionBlock,
type BetaCompactionBlockParam,
type BetaCompactionContentBlockDelta,
type BetaCompactionIterationUsage,
type BetaContainer,
type BetaContainerParams,
type BetaContainerUploadBlock,
type BetaContainerUploadBlockParam,
type BetaContentBlock,
type BetaContentBlockParam,
type BetaContentBlockSource,
type BetaContentBlockSourceContent,
type BetaContextManagementConfig,
type BetaContextManagementResponse,
type BetaCountTokensContextManagementResponse,
type BetaDirectCaller,
type BetaDocumentBlock,
type BetaEncryptedCodeExecutionResultBlock,
type BetaEncryptedCodeExecutionResultBlockParam,
type BetaFileDocumentSource,
type BetaFileImageSource,
type BetaImageBlockParam,
type BetaInputJSONDelta,
type BetaInputTokensClearAtLeast,
type BetaInputTokensTrigger,
type BetaJSONOutputFormat,
type BetaMCPToolConfig,
type BetaMCPToolDefaultConfig,
type BetaMCPToolResultBlock,
type BetaMCPToolUseBlock,
type BetaMCPToolUseBlockParam,
type BetaMCPToolset,
type BetaMemoryTool20250818,
type BetaMemoryTool20250818Command,
type BetaMemoryTool20250818CreateCommand,
type BetaMemoryTool20250818DeleteCommand,
type BetaMemoryTool20250818InsertCommand,
type BetaMemoryTool20250818RenameCommand,
type BetaMemoryTool20250818StrReplaceCommand,
type BetaMemoryTool20250818ViewCommand,
type BetaMessage,
type BetaMessageDeltaUsage,
type BetaMessageIterationUsage,
type BetaMessageParam,
type BetaMessageTokensCount,
type BetaMetadata,
type BetaOutputConfig,
type BetaPlainTextSource,
type BetaRawContentBlockDelta,
type BetaRawContentBlockDeltaEvent,
type BetaRawContentBlockStartEvent,
type BetaRawContentBlockStopEvent,
type BetaRawMessageDeltaEvent,
type BetaRawMessageStartEvent,
type BetaRawMessageStopEvent,
type BetaRawMessageStreamEvent,
type BetaRedactedThinkingBlock,
type BetaRedactedThinkingBlockParam,
type BetaRequestDocumentBlock,
type BetaRequestMCPServerToolConfiguration,
type BetaRequestMCPServerURLDefinition,
type BetaRequestMCPToolResultBlockParam,
type BetaSearchResultBlockParam,
type BetaServerToolCaller,
type BetaServerToolCaller20260120,
type BetaServerToolUsage,
type BetaServerToolUseBlock,
type BetaServerToolUseBlockParam,
type BetaSignatureDelta,
type BetaSkill,
type BetaSkillParams,
type BetaStopReason,
type BetaTextBlock,
type BetaTextBlockParam,
type BetaTextCitation,
type BetaTextCitationParam,
type BetaTextDelta,
type BetaTextEditorCodeExecutionCreateResultBlock,
type BetaTextEditorCodeExecutionCreateResultBlockParam,
type BetaTextEditorCodeExecutionStrReplaceResultBlock,
type BetaTextEditorCodeExecutionStrReplaceResultBlockParam,
type BetaTextEditorCodeExecutionToolResultBlock,
type BetaTextEditorCodeExecutionToolResultBlockParam,
type BetaTextEditorCodeExecutionToolResultError,
type BetaTextEditorCodeExecutionToolResultErrorParam,
type BetaTextEditorCodeExecutionViewResultBlock,
type BetaTextEditorCodeExecutionViewResultBlockParam,
type BetaThinkingBlock,
type BetaThinkingBlockParam,
type BetaThinkingConfigAdaptive,
type BetaThinkingConfigDisabled,
type BetaThinkingConfigEnabled,
type BetaThinkingConfigParam,
type BetaThinkingDelta,
type BetaThinkingTurns,
type BetaTool,
type BetaToolBash20241022,
type BetaToolBash20250124,
type BetaToolChoice,
type BetaToolChoiceAny,
type BetaToolChoiceAuto,
type BetaToolChoiceNone,
type BetaToolChoiceTool,
type BetaToolComputerUse20241022,
type BetaToolComputerUse20250124,
type BetaToolComputerUse20251124,
type BetaToolReferenceBlock,
type BetaToolReferenceBlockParam,
type BetaToolResultBlockParam,
type BetaToolSearchToolBm25_20251119,
type BetaToolSearchToolRegex20251119,
type BetaToolSearchToolResultBlock,
type BetaToolSearchToolResultBlockParam,
type BetaToolSearchToolResultError,
type BetaToolSearchToolResultErrorParam,
type BetaToolSearchToolSearchResultBlock,
type BetaToolSearchToolSearchResultBlockParam,
type BetaToolTextEditor20241022,
type BetaToolTextEditor20250124,
type BetaToolTextEditor20250429,
type BetaToolTextEditor20250728,
type BetaToolUnion,
type BetaToolUseBlock,
type BetaToolUseBlockParam,
type BetaToolUsesKeep,
type BetaToolUsesTrigger,
type BetaURLImageSource,
type BetaURLPDFSource,
type BetaUsage,
type BetaUserLocation,
type BetaWebFetchBlock,
type BetaWebFetchBlockParam,
type BetaWebFetchTool20250910,
type BetaWebFetchTool20260209,
type BetaWebFetchTool20260309,
type BetaWebFetchToolResultBlock,
type BetaWebFetchToolResultBlockParam,
type BetaWebFetchToolResultErrorBlock,
type BetaWebFetchToolResultErrorBlockParam,
type BetaWebFetchToolResultErrorCode,
type BetaWebSearchResultBlock,
type BetaWebSearchResultBlockParam,
type BetaWebSearchTool20250305,
type BetaWebSearchTool20260209,
type BetaWebSearchToolRequestError,
type BetaWebSearchToolResultBlock,
type BetaWebSearchToolResultBlockContent,
type BetaWebSearchToolResultBlockParam,
type BetaWebSearchToolResultBlockParamContent,
type BetaWebSearchToolResultError,
type BetaWebSearchToolResultErrorCode,
type BetaBase64PDFBlock,
type MessageCreateParams,
type MessageCreateParamsNonStreaming,
type MessageCreateParamsStreaming,
type MessageCountTokensParams,
type BetaToolResultContentBlockParam,
} from './messages/index';
export {
Models,
type BetaCapabilitySupport,
type BetaContextManagementCapability,
type BetaEffortCapability,
type BetaModelCapabilities,
type BetaModelInfo,
type BetaThinkingCapability,
type BetaThinkingTypes,
type ModelRetrieveParams,
type ModelListParams,
type BetaModelInfosPage,
} from './models';
export {
Skills,
type SkillCreateResponse,
type SkillRetrieveResponse,
type SkillListResponse,
type SkillDeleteResponse,
type SkillCreateParams,
type SkillRetrieveParams,
type SkillListParams,
type SkillDeleteParams,
type SkillListResponsesPageCursor,
} from './skills/index';

View File

@@ -0,0 +1,3 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export * from './messages/index';

View File

@@ -0,0 +1,827 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from '../../../core/resource';
import * as BetaAPI from '../beta';
import { APIPromise } from '../../../core/api-promise';
import * as BetaMessagesAPI from './messages';
import { Page, type PageParams, PagePromise } from '../../../core/pagination';
import { buildHeaders } from '../../../internal/headers';
import { RequestOptions } from '../../../internal/request-options';
import { JSONLDecoder } from '../../../internal/decoders/jsonl';
import { AnthropicError } from '../../../error';
import { path } from '../../../internal/utils/path';
import * as MessagesApi from '../../messages/messages';
export class Batches extends APIResource {
/**
* Send a batch of Message creation requests.
*
* The Message Batches API can be used to process multiple Messages API requests at
* once. Once a Message Batch is created, it begins processing immediately. Batches
* can take up to 24 hours to complete.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const betaMessageBatch =
* await client.beta.messages.batches.create({
* requests: [
* {
* custom_id: 'my-custom-id-1',
* params: {
* max_tokens: 1024,
* messages: [
* { content: 'Hello, world', role: 'user' },
* ],
* model: 'claude-opus-4-6',
* },
* },
* ],
* });
* ```
*/
create(params: BatchCreateParams, options?: RequestOptions): APIPromise<BetaMessageBatch> {
const { betas, ...body } = params;
return this._client.post('/v1/messages/batches?beta=true', {
body,
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'message-batches-2024-09-24'].toString() },
options?.headers,
]),
});
}
/**
* This endpoint is idempotent and can be used to poll for Message Batch
* completion. To access the results of a Message Batch, make a request to the
* `results_url` field in the response.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const betaMessageBatch =
* await client.beta.messages.batches.retrieve(
* 'message_batch_id',
* );
* ```
*/
retrieve(
messageBatchID: string,
params: BatchRetrieveParams | null | undefined = {},
options?: RequestOptions,
): APIPromise<BetaMessageBatch> {
const { betas } = params ?? {};
return this._client.get(path`/v1/messages/batches/${messageBatchID}?beta=true`, {
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'message-batches-2024-09-24'].toString() },
options?.headers,
]),
});
}
/**
* List all Message Batches within a Workspace. Most recently created batches are
* returned first.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* // Automatically fetches more pages as needed.
* for await (const betaMessageBatch of client.beta.messages.batches.list()) {
* // ...
* }
* ```
*/
list(
params: BatchListParams | null | undefined = {},
options?: RequestOptions,
): PagePromise<BetaMessageBatchesPage, BetaMessageBatch> {
const { betas, ...query } = params ?? {};
return this._client.getAPIList('/v1/messages/batches?beta=true', Page<BetaMessageBatch>, {
query,
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'message-batches-2024-09-24'].toString() },
options?.headers,
]),
});
}
/**
* Delete a Message Batch.
*
* Message Batches can only be deleted once they've finished processing. If you'd
* like to delete an in-progress batch, you must first cancel it.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const betaDeletedMessageBatch =
* await client.beta.messages.batches.delete(
* 'message_batch_id',
* );
* ```
*/
delete(
messageBatchID: string,
params: BatchDeleteParams | null | undefined = {},
options?: RequestOptions,
): APIPromise<BetaDeletedMessageBatch> {
const { betas } = params ?? {};
return this._client.delete(path`/v1/messages/batches/${messageBatchID}?beta=true`, {
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'message-batches-2024-09-24'].toString() },
options?.headers,
]),
});
}
/**
* Batches may be canceled any time before processing ends. Once cancellation is
* initiated, the batch enters a `canceling` state, at which time the system may
* complete any in-progress, non-interruptible requests before finalizing
* cancellation.
*
* The number of canceled requests is specified in `request_counts`. To determine
* which requests were canceled, check the individual results within the batch.
* Note that cancellation may not result in any canceled requests if they were
* non-interruptible.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const betaMessageBatch =
* await client.beta.messages.batches.cancel(
* 'message_batch_id',
* );
* ```
*/
cancel(
messageBatchID: string,
params: BatchCancelParams | null | undefined = {},
options?: RequestOptions,
): APIPromise<BetaMessageBatch> {
const { betas } = params ?? {};
return this._client.post(path`/v1/messages/batches/${messageBatchID}/cancel?beta=true`, {
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'message-batches-2024-09-24'].toString() },
options?.headers,
]),
});
}
/**
* Streams the results of a Message Batch as a `.jsonl` file.
*
* Each line in the file is a JSON object containing the result of a single request
* in the Message Batch. Results are not guaranteed to be in the same order as
* requests. Use the `custom_id` field to match results to requests.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const betaMessageBatchIndividualResponse =
* await client.beta.messages.batches.results(
* 'message_batch_id',
* );
* ```
*/
async results(
messageBatchID: string,
params: BatchResultsParams | undefined = {},
options?: RequestOptions,
): Promise<JSONLDecoder<BetaMessageBatchIndividualResponse>> {
const batch = await this.retrieve(messageBatchID);
if (!batch.results_url) {
throw new AnthropicError(
`No batch \`results_url\`; Has it finished processing? ${batch.processing_status} - ${batch.id}`,
);
}
const { betas } = params ?? {};
return this._client
.get(batch.results_url, {
...options,
headers: buildHeaders([
{
'anthropic-beta': [...(betas ?? []), 'message-batches-2024-09-24'].toString(),
Accept: 'application/binary',
},
options?.headers,
]),
stream: true,
__binaryResponse: true,
})
._thenUnwrap((_, props) => JSONLDecoder.fromResponse(props.response, props.controller)) as APIPromise<
JSONLDecoder<BetaMessageBatchIndividualResponse>
>;
}
}
export type BetaMessageBatchesPage = Page<BetaMessageBatch>;
export interface BetaDeletedMessageBatch {
/**
* ID of the Message Batch.
*/
id: string;
/**
* Deleted object type.
*
* For Message Batches, this is always `"message_batch_deleted"`.
*/
type: 'message_batch_deleted';
}
export interface BetaMessageBatch {
/**
* Unique object identifier.
*
* The format and length of IDs may change over time.
*/
id: string;
/**
* RFC 3339 datetime string representing the time at which the Message Batch was
* archived and its results became unavailable.
*/
archived_at: string | null;
/**
* RFC 3339 datetime string representing the time at which cancellation was
* initiated for the Message Batch. Specified only if cancellation was initiated.
*/
cancel_initiated_at: string | null;
/**
* RFC 3339 datetime string representing the time at which the Message Batch was
* created.
*/
created_at: string;
/**
* RFC 3339 datetime string representing the time at which processing for the
* Message Batch ended. Specified only once processing ends.
*
* Processing ends when every request in a Message Batch has either succeeded,
* errored, canceled, or expired.
*/
ended_at: string | null;
/**
* RFC 3339 datetime string representing the time at which the Message Batch will
* expire and end processing, which is 24 hours after creation.
*/
expires_at: string;
/**
* Processing status of the Message Batch.
*/
processing_status: 'in_progress' | 'canceling' | 'ended';
/**
* Tallies requests within the Message Batch, categorized by their status.
*
* Requests start as `processing` and move to one of the other statuses only once
* processing of the entire batch ends. The sum of all values always matches the
* total number of requests in the batch.
*/
request_counts: BetaMessageBatchRequestCounts;
/**
* URL to a `.jsonl` file containing the results of the Message Batch requests.
* Specified only once processing ends.
*
* Results in the file are not guaranteed to be in the same order as requests. Use
* the `custom_id` field to match results to requests.
*/
results_url: string | null;
/**
* Object type.
*
* For Message Batches, this is always `"message_batch"`.
*/
type: 'message_batch';
}
export interface BetaMessageBatchCanceledResult {
type: 'canceled';
}
export interface BetaMessageBatchErroredResult {
error: BetaAPI.BetaErrorResponse;
type: 'errored';
}
export interface BetaMessageBatchExpiredResult {
type: 'expired';
}
/**
* This is a single line in the response `.jsonl` file and does not represent the
* response as a whole.
*/
export interface BetaMessageBatchIndividualResponse {
/**
* Developer-provided ID created for each request in a Message Batch. Useful for
* matching results to requests, as results may be given out of request order.
*
* Must be unique for each request within the Message Batch.
*/
custom_id: string;
/**
* Processing result for this request.
*
* Contains a Message output if processing was successful, an error response if
* processing failed, or the reason why processing was not attempted, such as
* cancellation or expiration.
*/
result: BetaMessageBatchResult;
}
export interface BetaMessageBatchRequestCounts {
/**
* Number of requests in the Message Batch that have been canceled.
*
* This is zero until processing of the entire Message Batch has ended.
*/
canceled: number;
/**
* Number of requests in the Message Batch that encountered an error.
*
* This is zero until processing of the entire Message Batch has ended.
*/
errored: number;
/**
* Number of requests in the Message Batch that have expired.
*
* This is zero until processing of the entire Message Batch has ended.
*/
expired: number;
/**
* Number of requests in the Message Batch that are processing.
*/
processing: number;
/**
* Number of requests in the Message Batch that have completed successfully.
*
* This is zero until processing of the entire Message Batch has ended.
*/
succeeded: number;
}
/**
* Processing result for this request.
*
* Contains a Message output if processing was successful, an error response if
* processing failed, or the reason why processing was not attempted, such as
* cancellation or expiration.
*/
export type BetaMessageBatchResult =
| BetaMessageBatchSucceededResult
| BetaMessageBatchErroredResult
| BetaMessageBatchCanceledResult
| BetaMessageBatchExpiredResult;
export interface BetaMessageBatchSucceededResult {
message: BetaMessagesAPI.BetaMessage;
type: 'succeeded';
}
export interface BatchCreateParams {
/**
* Body param: List of requests for prompt completion. Each is an individual
* request to create a Message.
*/
requests: Array<BatchCreateParams.Request>;
/**
* Header param: Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export namespace BatchCreateParams {
export interface Request {
/**
* Developer-provided ID created for each request in a Message Batch. Useful for
* matching results to requests, as results may be given out of request order.
*
* Must be unique for each request within the Message Batch.
*/
custom_id: string;
/**
* Messages API creation parameters for the individual request.
*
* See the [Messages API reference](https://docs.claude.com/en/api/messages) for
* full documentation on available parameters.
*/
params: Request.Params;
}
export namespace Request {
/**
* Messages API creation parameters for the individual request.
*
* See the [Messages API reference](https://docs.claude.com/en/api/messages) for
* full documentation on available parameters.
*/
export interface Params {
/**
* The maximum number of tokens to generate before stopping.
*
* Note that our models may stop _before_ reaching this maximum. This parameter
* only specifies the absolute maximum number of tokens to generate.
*
* Different models have different maximum values for this parameter. See
* [models](https://docs.claude.com/en/docs/models-overview) for details.
*/
max_tokens: number;
/**
* Input messages.
*
* Our models are trained to operate on alternating `user` and `assistant`
* conversational turns. When creating a new `Message`, you specify the prior
* conversational turns with the `messages` parameter, and the model then generates
* the next `Message` in the conversation. Consecutive `user` or `assistant` turns
* in your request will be combined into a single turn.
*
* Each input message must be an object with a `role` and `content`. You can
* specify a single `user`-role message, or you can include multiple `user` and
* `assistant` messages.
*
* If the final message uses the `assistant` role, the response content will
* continue immediately from the content in that message. This can be used to
* constrain part of the model's response.
*
* Example with a single `user` message:
*
* ```json
* [{ "role": "user", "content": "Hello, Claude" }]
* ```
*
* Example with multiple conversational turns:
*
* ```json
* [
* { "role": "user", "content": "Hello there." },
* { "role": "assistant", "content": "Hi, I'm Claude. How can I help you?" },
* { "role": "user", "content": "Can you explain LLMs in plain English?" }
* ]
* ```
*
* Example with a partially-filled response from Claude:
*
* ```json
* [
* {
* "role": "user",
* "content": "What's the Greek name for Sun? (A) Sol (B) Helios (C) Sun"
* },
* { "role": "assistant", "content": "The best answer is (" }
* ]
* ```
*
* Each input message `content` may be either a single `string` or an array of
* content blocks, where each block has a specific `type`. Using a `string` for
* `content` is shorthand for an array of one content block of type `"text"`. The
* following input messages are equivalent:
*
* ```json
* { "role": "user", "content": "Hello, Claude" }
* ```
*
* ```json
* { "role": "user", "content": [{ "type": "text", "text": "Hello, Claude" }] }
* ```
*
* See [input examples](https://docs.claude.com/en/api/messages-examples).
*
* Note that if you want to include a
* [system prompt](https://docs.claude.com/en/docs/system-prompts), you can use the
* top-level `system` parameter — there is no `"system"` role for input messages in
* the Messages API.
*
* There is a limit of 100,000 messages in a single request.
*/
messages: Array<BetaMessagesAPI.BetaMessageParam>;
/**
* The model that will complete your prompt.\n\nSee
* [models](https://docs.anthropic.com/en/docs/models-overview) for additional
* details and options.
*/
model: MessagesApi.Model;
/**
* Top-level cache control automatically applies a cache_control marker to the last
* cacheable block in the request.
*/
cache_control?: BetaMessagesAPI.BetaCacheControlEphemeral | null;
/**
* Container identifier for reuse across requests.
*/
container?: BetaMessagesAPI.BetaContainerParams | string | null;
/**
* Context management configuration.
*
* This allows you to control how Claude manages context across multiple requests,
* such as whether to clear function results or not.
*/
context_management?: BetaMessagesAPI.BetaContextManagementConfig | null;
/**
* Specifies the geographic region for inference processing. If not specified, the
* workspace's `default_inference_geo` is used.
*/
inference_geo?: string | null;
/**
* MCP servers to be utilized in this request
*/
mcp_servers?: Array<BetaMessagesAPI.BetaRequestMCPServerURLDefinition>;
/**
* An object describing metadata about the request.
*/
metadata?: BetaMessagesAPI.BetaMetadata;
/**
* Configuration options for the model's output, such as the output format.
*/
output_config?: BetaMessagesAPI.BetaOutputConfig;
/**
* @deprecated Deprecated: Use `output_config.format` instead. See
* [structured outputs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs)
*
* A schema to specify Claude's output format in responses. This parameter will be
* removed in a future release.
*/
output_format?: BetaMessagesAPI.BetaJSONOutputFormat | null;
/**
* Determines whether to use priority capacity (if available) or standard capacity
* for this request.
*
* Anthropic offers different levels of service for your API requests. See
* [service-tiers](https://docs.claude.com/en/api/service-tiers) for details.
*/
service_tier?: 'auto' | 'standard_only';
/**
* The inference speed mode for this request. `"fast"` enables high
* output-tokens-per-second inference.
*/
speed?: 'standard' | 'fast' | null;
/**
* Custom text sequences that will cause the model to stop generating.
*
* Our models will normally stop when they have naturally completed their turn,
* which will result in a response `stop_reason` of `"end_turn"`.
*
* If you want the model to stop generating when it encounters custom strings of
* text, you can use the `stop_sequences` parameter. If the model encounters one of
* the custom sequences, the response `stop_reason` value will be `"stop_sequence"`
* and the response `stop_sequence` value will contain the matched stop sequence.
*/
stop_sequences?: Array<string>;
/**
* Whether to incrementally stream the response using server-sent events.
*
* See [streaming](https://docs.claude.com/en/api/messages-streaming) for details.
*/
stream?: boolean;
/**
* System prompt.
*
* A system prompt is a way of providing context and instructions to Claude, such
* as specifying a particular goal or role. See our
* [guide to system prompts](https://docs.claude.com/en/docs/system-prompts).
*/
system?: string | Array<BetaMessagesAPI.BetaTextBlockParam>;
/**
* Amount of randomness injected into the response.
*
* Defaults to `1.0`. Ranges from `0.0` to `1.0`. Use `temperature` closer to `0.0`
* for analytical / multiple choice, and closer to `1.0` for creative and
* generative tasks.
*
* Note that even with `temperature` of `0.0`, the results will not be fully
* deterministic.
*/
temperature?: number;
/**
* Configuration for enabling Claude's extended thinking.
*
* When enabled, responses include `thinking` content blocks showing Claude's
* thinking process before the final answer. Requires a minimum budget of 1,024
* tokens and counts towards your `max_tokens` limit.
*
* See
* [extended thinking](https://docs.claude.com/en/docs/build-with-claude/extended-thinking)
* for details.
*/
thinking?: BetaMessagesAPI.BetaThinkingConfigParam;
/**
* How the model should use the provided tools. The model can use a specific tool,
* any available tool, decide by itself, or not use tools at all.
*/
tool_choice?: BetaMessagesAPI.BetaToolChoice;
/**
* Definitions of tools that the model may use.
*
* If you include `tools` in your API request, the model may return `tool_use`
* content blocks that represent the model's use of those tools. You can then run
* those tools using the tool input generated by the model and then optionally
* return results back to the model using `tool_result` content blocks.
*
* There are two types of tools: **client tools** and **server tools**. The
* behavior described below applies to client tools. For
* [server tools](https://docs.claude.com/en/docs/agents-and-tools/tool-use/overview#server-tools),
* see their individual documentation as each has its own behavior (e.g., the
* [web search tool](https://docs.claude.com/en/docs/agents-and-tools/tool-use/web-search-tool)).
*
* Each tool definition includes:
*
* - `name`: Name of the tool.
* - `description`: Optional, but strongly-recommended description of the tool.
* - `input_schema`: [JSON schema](https://json-schema.org/draft/2020-12) for the
* tool `input` shape that the model will produce in `tool_use` output content
* blocks.
*
* For example, if you defined `tools` as:
*
* ```json
* [
* {
* "name": "get_stock_price",
* "description": "Get the current stock price for a given ticker symbol.",
* "input_schema": {
* "type": "object",
* "properties": {
* "ticker": {
* "type": "string",
* "description": "The stock ticker symbol, e.g. AAPL for Apple Inc."
* }
* },
* "required": ["ticker"]
* }
* }
* ]
* ```
*
* And then asked the model "What's the S&P 500 at today?", the model might produce
* `tool_use` content blocks in the response like this:
*
* ```json
* [
* {
* "type": "tool_use",
* "id": "toolu_01D7FLrfh4GYq7yT1ULFeyMV",
* "name": "get_stock_price",
* "input": { "ticker": "^GSPC" }
* }
* ]
* ```
*
* You might then run your `get_stock_price` tool with `{"ticker": "^GSPC"}` as an
* input, and return the following back to the model in a subsequent `user`
* message:
*
* ```json
* [
* {
* "type": "tool_result",
* "tool_use_id": "toolu_01D7FLrfh4GYq7yT1ULFeyMV",
* "content": "259.75 USD"
* }
* ]
* ```
*
* Tools can be used for workflows that include running client-side tools and
* functions, or more generally whenever you want the model to produce a particular
* JSON structure of output.
*
* See our [guide](https://docs.claude.com/en/docs/tool-use) for more details.
*/
tools?: Array<BetaMessagesAPI.BetaToolUnion>;
/**
* Only sample from the top K options for each subsequent token.
*
* Used to remove "long tail" low probability responses.
* [Learn more technical details here](https://towardsdatascience.com/how-to-sample-from-language-models-682bceb97277).
*
* Recommended for advanced use cases only. You usually only need to use
* `temperature`.
*/
top_k?: number;
/**
* Use nucleus sampling.
*
* In nucleus sampling, we compute the cumulative distribution over all the options
* for each subsequent token in decreasing probability order and cut it off once it
* reaches a particular probability specified by `top_p`. You should either alter
* `temperature` or `top_p`, but not both.
*
* Recommended for advanced use cases only. You usually only need to use
* `temperature`.
*/
top_p?: number;
}
}
}
export interface BatchRetrieveParams {
/**
* Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export interface BatchListParams extends PageParams {
/**
* Header param: Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export interface BatchDeleteParams {
/**
* Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export interface BatchCancelParams {
/**
* Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export interface BatchResultsParams {
/**
* Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export declare namespace Batches {
export {
type BetaDeletedMessageBatch as BetaDeletedMessageBatch,
type BetaMessageBatch as BetaMessageBatch,
type BetaMessageBatchCanceledResult as BetaMessageBatchCanceledResult,
type BetaMessageBatchErroredResult as BetaMessageBatchErroredResult,
type BetaMessageBatchExpiredResult as BetaMessageBatchExpiredResult,
type BetaMessageBatchIndividualResponse as BetaMessageBatchIndividualResponse,
type BetaMessageBatchRequestCounts as BetaMessageBatchRequestCounts,
type BetaMessageBatchResult as BetaMessageBatchResult,
type BetaMessageBatchSucceededResult as BetaMessageBatchSucceededResult,
type BetaMessageBatchesPage as BetaMessageBatchesPage,
type BatchCreateParams as BatchCreateParams,
type BatchRetrieveParams as BatchRetrieveParams,
type BatchListParams as BatchListParams,
type BatchDeleteParams as BatchDeleteParams,
type BatchCancelParams as BatchCancelParams,
type BatchResultsParams as BatchResultsParams,
};
}

View File

@@ -0,0 +1,228 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export {
Batches,
type BetaDeletedMessageBatch,
type BetaMessageBatch,
type BetaMessageBatchCanceledResult,
type BetaMessageBatchErroredResult,
type BetaMessageBatchExpiredResult,
type BetaMessageBatchIndividualResponse,
type BetaMessageBatchRequestCounts,
type BetaMessageBatchResult,
type BetaMessageBatchSucceededResult,
type BatchCreateParams,
type BatchRetrieveParams,
type BatchListParams,
type BatchDeleteParams,
type BatchCancelParams,
type BatchResultsParams,
type BetaMessageBatchesPage,
} from './batches';
export {
Messages,
type BetaAllThinkingTurns,
type BetaBase64ImageSource,
type BetaBase64PDFSource,
type BetaBashCodeExecutionOutputBlock,
type BetaBashCodeExecutionOutputBlockParam,
type BetaBashCodeExecutionResultBlock,
type BetaBashCodeExecutionResultBlockParam,
type BetaBashCodeExecutionToolResultBlock,
type BetaBashCodeExecutionToolResultBlockParam,
type BetaBashCodeExecutionToolResultError,
type BetaBashCodeExecutionToolResultErrorParam,
type BetaCacheControlEphemeral,
type BetaCacheCreation,
type BetaCitationCharLocation,
type BetaCitationCharLocationParam,
type BetaCitationConfig,
type BetaCitationContentBlockLocation,
type BetaCitationContentBlockLocationParam,
type BetaCitationPageLocation,
type BetaCitationPageLocationParam,
type BetaCitationSearchResultLocation,
type BetaCitationSearchResultLocationParam,
type BetaCitationWebSearchResultLocationParam,
type BetaCitationsConfigParam,
type BetaCitationsDelta,
type BetaCitationsWebSearchResultLocation,
type BetaClearThinking20251015Edit,
type BetaClearThinking20251015EditResponse,
type BetaClearToolUses20250919Edit,
type BetaClearToolUses20250919EditResponse,
type BetaCodeExecutionOutputBlock,
type BetaCodeExecutionOutputBlockParam,
type BetaCodeExecutionResultBlock,
type BetaCodeExecutionResultBlockParam,
type BetaCodeExecutionTool20250522,
type BetaCodeExecutionTool20250825,
type BetaCodeExecutionTool20260120,
type BetaCodeExecutionToolResultBlock,
type BetaCodeExecutionToolResultBlockContent,
type BetaCodeExecutionToolResultBlockParam,
type BetaCodeExecutionToolResultBlockParamContent,
type BetaCodeExecutionToolResultError,
type BetaCodeExecutionToolResultErrorCode,
type BetaCodeExecutionToolResultErrorParam,
type BetaCompact20260112Edit,
type BetaCompactionBlock,
type BetaCompactionBlockParam,
type BetaCompactionContentBlockDelta,
type BetaCompactionIterationUsage,
type BetaContainer,
type BetaContainerParams,
type BetaContainerUploadBlock,
type BetaContainerUploadBlockParam,
type BetaContentBlock,
type BetaContentBlockParam,
type BetaContentBlockSource,
type BetaContentBlockSourceContent,
type BetaContextManagementConfig,
type BetaContextManagementResponse,
type BetaCountTokensContextManagementResponse,
type BetaDirectCaller,
type BetaDocumentBlock,
type BetaEncryptedCodeExecutionResultBlock,
type BetaEncryptedCodeExecutionResultBlockParam,
type BetaFileDocumentSource,
type BetaFileImageSource,
type BetaImageBlockParam,
type BetaInputJSONDelta,
type BetaInputTokensClearAtLeast,
type BetaInputTokensTrigger,
type BetaJSONOutputFormat,
type BetaMCPToolResultBlock,
type BetaMCPToolUseBlock,
type BetaMCPToolUseBlockParam,
type BetaMCPToolset,
type BetaMemoryTool20250818,
type BetaMemoryTool20250818Command,
type BetaMemoryTool20250818CreateCommand,
type BetaMemoryTool20250818DeleteCommand,
type BetaMemoryTool20250818InsertCommand,
type BetaMemoryTool20250818RenameCommand,
type BetaMemoryTool20250818StrReplaceCommand,
type BetaMemoryTool20250818ViewCommand,
type BetaMessage,
type BetaMessageDeltaUsage,
type BetaMessageIterationUsage,
type BetaMessageParam,
type BetaMessageTokensCount,
type BetaMetadata,
type BetaOutputConfig,
type BetaPlainTextSource,
type BetaRawContentBlockDelta,
type BetaRawContentBlockDeltaEvent,
type BetaRawContentBlockStartEvent,
type BetaRawContentBlockStopEvent,
type BetaRawMessageDeltaEvent,
type BetaRawMessageStartEvent,
type BetaRawMessageStopEvent,
type BetaRawMessageStreamEvent,
type BetaRedactedThinkingBlock,
type BetaRedactedThinkingBlockParam,
type BetaRequestDocumentBlock,
type BetaRequestMCPServerToolConfiguration,
type BetaRequestMCPServerURLDefinition,
type BetaRequestMCPToolResultBlockParam,
type BetaSearchResultBlockParam,
type BetaServerToolCaller,
type BetaServerToolCaller20260120,
type BetaServerToolUsage,
type BetaServerToolUseBlock,
type BetaServerToolUseBlockParam,
type BetaSignatureDelta,
type BetaSkill,
type BetaSkillParams,
type BetaStopReason,
type BetaTextBlock,
type BetaTextBlockParam,
type BetaTextCitation,
type BetaTextCitationParam,
type BetaTextDelta,
type BetaTextEditorCodeExecutionCreateResultBlock,
type BetaTextEditorCodeExecutionCreateResultBlockParam,
type BetaTextEditorCodeExecutionStrReplaceResultBlock,
type BetaTextEditorCodeExecutionStrReplaceResultBlockParam,
type BetaTextEditorCodeExecutionToolResultBlock,
type BetaTextEditorCodeExecutionToolResultBlockParam,
type BetaTextEditorCodeExecutionToolResultError,
type BetaTextEditorCodeExecutionToolResultErrorParam,
type BetaTextEditorCodeExecutionViewResultBlock,
type BetaTextEditorCodeExecutionViewResultBlockParam,
type BetaThinkingBlock,
type BetaThinkingBlockParam,
type BetaThinkingConfigAdaptive,
type BetaThinkingConfigDisabled,
type BetaThinkingConfigEnabled,
type BetaThinkingConfigParam,
type BetaThinkingDelta,
type BetaThinkingTurns,
type BetaTool,
type BetaToolBash20241022,
type BetaToolBash20250124,
type BetaToolChoice,
type BetaToolChoiceAny,
type BetaToolChoiceAuto,
type BetaToolChoiceNone,
type BetaToolChoiceTool,
type BetaToolComputerUse20241022,
type BetaToolComputerUse20250124,
type BetaToolComputerUse20251124,
type BetaToolReferenceBlock,
type BetaToolReferenceBlockParam,
type BetaToolResultBlockParam,
type BetaToolTextEditor20241022,
type BetaToolTextEditor20250124,
type BetaToolTextEditor20250429,
type BetaToolTextEditor20250728,
type BetaToolUnion,
type BetaToolUseBlock,
type BetaToolUseBlockParam,
type BetaToolUsesKeep,
type BetaToolUsesTrigger,
type BetaURLImageSource,
type BetaURLPDFSource,
type BetaUsage,
type BetaUserLocation,
type BetaWebFetchBlock,
type BetaWebFetchBlockParam,
type BetaWebFetchTool20250910,
type BetaWebFetchTool20260209,
type BetaWebFetchTool20260309,
type BetaWebFetchToolResultBlock,
type BetaWebFetchToolResultBlockParam,
type BetaWebFetchToolResultErrorBlock,
type BetaWebFetchToolResultErrorBlockParam,
type BetaWebFetchToolResultErrorCode,
type BetaWebSearchResultBlock,
type BetaWebSearchResultBlockParam,
type BetaWebSearchTool20250305,
type BetaWebSearchTool20260209,
type BetaWebSearchToolRequestError,
type BetaWebSearchToolResultBlock,
type BetaWebSearchToolResultBlockContent,
type BetaWebSearchToolResultBlockParam,
type BetaWebSearchToolResultBlockParamContent,
type BetaWebSearchToolResultError,
type BetaWebSearchToolResultErrorCode,
type BetaBase64PDFBlock,
type MessageCreateParams,
type MessageCreateParamsNonStreaming,
type MessageCreateParamsStreaming,
type MessageCountTokensParams,
type BetaMessageStreamParams,
type BetaToolSearchToolBm25_20251119,
type BetaToolSearchToolRegex20251119,
type BetaToolSearchToolResultBlock,
type BetaToolSearchToolResultBlockParam,
type BetaToolSearchToolResultError,
type BetaToolSearchToolResultErrorParam,
type BetaToolSearchToolSearchResultBlock,
type BetaToolSearchToolSearchResultBlockParam,
type BetaMCPToolConfig,
type BetaMCPToolDefaultConfig,
type BetaToolResultContentBlockParam,
} from './messages';
export { BetaToolRunner, type BetaToolRunnerParams, ToolError } from './messages';

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,284 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from '../../core/resource';
import * as BetaAPI from './beta';
import { APIPromise } from '../../core/api-promise';
import { Page, type PageParams, PagePromise } from '../../core/pagination';
import { buildHeaders } from '../../internal/headers';
import { RequestOptions } from '../../internal/request-options';
import { path } from '../../internal/utils/path';
export class Models extends APIResource {
/**
* Get a specific model.
*
* The Models API response can be used to determine information about a specific
* model or resolve a model alias to a model ID.
*
* @example
* ```ts
* const betaModelInfo = await client.beta.models.retrieve(
* 'model_id',
* );
* ```
*/
retrieve(
modelID: string,
params: ModelRetrieveParams | null | undefined = {},
options?: RequestOptions,
): APIPromise<BetaModelInfo> {
const { betas } = params ?? {};
return this._client.get(path`/v1/models/${modelID}?beta=true`, {
...options,
headers: buildHeaders([
{ ...(betas?.toString() != null ? { 'anthropic-beta': betas?.toString() } : undefined) },
options?.headers,
]),
});
}
/**
* List available models.
*
* The Models API response can be used to determine which models are available for
* use in the API. More recently released models are listed first.
*
* @example
* ```ts
* // Automatically fetches more pages as needed.
* for await (const betaModelInfo of client.beta.models.list()) {
* // ...
* }
* ```
*/
list(
params: ModelListParams | null | undefined = {},
options?: RequestOptions,
): PagePromise<BetaModelInfosPage, BetaModelInfo> {
const { betas, ...query } = params ?? {};
return this._client.getAPIList('/v1/models?beta=true', Page<BetaModelInfo>, {
query,
...options,
headers: buildHeaders([
{ ...(betas?.toString() != null ? { 'anthropic-beta': betas?.toString() } : undefined) },
options?.headers,
]),
});
}
}
export type BetaModelInfosPage = Page<BetaModelInfo>;
/**
* Indicates whether a capability is supported.
*/
export interface BetaCapabilitySupport {
/**
* Whether this capability is supported by the model.
*/
supported: boolean;
}
/**
* Context management capability details.
*/
export interface BetaContextManagementCapability {
/**
* Indicates whether a capability is supported.
*/
clear_thinking_20251015: BetaCapabilitySupport | null;
/**
* Indicates whether a capability is supported.
*/
clear_tool_uses_20250919: BetaCapabilitySupport | null;
/**
* Indicates whether a capability is supported.
*/
compact_20260112: BetaCapabilitySupport | null;
/**
* Whether this capability is supported by the model.
*/
supported: boolean;
}
/**
* Effort (reasoning_effort) capability details.
*/
export interface BetaEffortCapability {
/**
* Whether the model supports high effort level.
*/
high: BetaCapabilitySupport;
/**
* Whether the model supports low effort level.
*/
low: BetaCapabilitySupport;
/**
* Whether the model supports max effort level.
*/
max: BetaCapabilitySupport;
/**
* Whether the model supports medium effort level.
*/
medium: BetaCapabilitySupport;
/**
* Whether this capability is supported by the model.
*/
supported: boolean;
}
/**
* Model capability information.
*/
export interface BetaModelCapabilities {
/**
* Whether the model supports the Batch API.
*/
batch: BetaCapabilitySupport;
/**
* Whether the model supports citation generation.
*/
citations: BetaCapabilitySupport;
/**
* Whether the model supports code execution tools.
*/
code_execution: BetaCapabilitySupport;
/**
* Context management support and available strategies.
*/
context_management: BetaContextManagementCapability;
/**
* Effort (reasoning_effort) support and available levels.
*/
effort: BetaEffortCapability;
/**
* Whether the model accepts image content blocks.
*/
image_input: BetaCapabilitySupport;
/**
* Whether the model accepts PDF content blocks.
*/
pdf_input: BetaCapabilitySupport;
/**
* Whether the model supports structured output / JSON mode / strict tool schemas.
*/
structured_outputs: BetaCapabilitySupport;
/**
* Thinking capability and supported type configurations.
*/
thinking: BetaThinkingCapability;
}
export interface BetaModelInfo {
/**
* Unique model identifier.
*/
id: string;
/**
* Model capability information.
*/
capabilities: BetaModelCapabilities | null;
/**
* RFC 3339 datetime string representing the time at which the model was released.
* May be set to an epoch value if the release date is unknown.
*/
created_at: string;
/**
* A human-readable name for the model.
*/
display_name: string;
/**
* Maximum input context window size in tokens for this model.
*/
max_input_tokens: number | null;
/**
* Maximum value for the `max_tokens` parameter when using this model.
*/
max_tokens: number | null;
/**
* Object type.
*
* For Models, this is always `"model"`.
*/
type: 'model';
}
/**
* Thinking capability details.
*/
export interface BetaThinkingCapability {
/**
* Whether this capability is supported by the model.
*/
supported: boolean;
/**
* Supported thinking type configurations.
*/
types: BetaThinkingTypes;
}
/**
* Supported thinking type configurations.
*/
export interface BetaThinkingTypes {
/**
* Whether the model supports thinking with type 'adaptive' (auto).
*/
adaptive: BetaCapabilitySupport;
/**
* Whether the model supports thinking with type 'enabled'.
*/
enabled: BetaCapabilitySupport;
}
export interface ModelRetrieveParams {
/**
* Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export interface ModelListParams extends PageParams {
/**
* Header param: Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export declare namespace Models {
export {
type BetaCapabilitySupport as BetaCapabilitySupport,
type BetaContextManagementCapability as BetaContextManagementCapability,
type BetaEffortCapability as BetaEffortCapability,
type BetaModelCapabilities as BetaModelCapabilities,
type BetaModelInfo as BetaModelInfo,
type BetaThinkingCapability as BetaThinkingCapability,
type BetaThinkingTypes as BetaThinkingTypes,
type BetaModelInfosPage as BetaModelInfosPage,
type ModelRetrieveParams as ModelRetrieveParams,
type ModelListParams as ModelListParams,
};
}

View File

@@ -0,0 +1,3 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export * from './skills/index';

View File

@@ -0,0 +1,26 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export {
Skills,
type SkillCreateResponse,
type SkillRetrieveResponse,
type SkillListResponse,
type SkillDeleteResponse,
type SkillCreateParams,
type SkillRetrieveParams,
type SkillListParams,
type SkillDeleteParams,
type SkillListResponsesPageCursor,
} from './skills';
export {
Versions,
type VersionCreateResponse,
type VersionRetrieveResponse,
type VersionListResponse,
type VersionDeleteResponse,
type VersionCreateParams,
type VersionRetrieveParams,
type VersionListParams,
type VersionDeleteParams,
type VersionListResponsesPageCursor,
} from './versions';

View File

@@ -0,0 +1,384 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from '../../../core/resource';
import * as BetaAPI from '../beta';
import * as VersionsAPI from './versions';
import {
VersionCreateParams,
VersionCreateResponse,
VersionDeleteParams,
VersionDeleteResponse,
VersionListParams,
VersionListResponse,
VersionListResponsesPageCursor,
VersionRetrieveParams,
VersionRetrieveResponse,
Versions,
} from './versions';
import { APIPromise } from '../../../core/api-promise';
import { PageCursor, type PageCursorParams, PagePromise } from '../../../core/pagination';
import { type Uploadable } from '../../../core/uploads';
import { buildHeaders } from '../../../internal/headers';
import { RequestOptions } from '../../../internal/request-options';
import { multipartFormRequestOptions } from '../../../internal/uploads';
import { path } from '../../../internal/utils/path';
export class Skills extends APIResource {
versions: VersionsAPI.Versions = new VersionsAPI.Versions(this._client);
/**
* Create Skill
*
* @example
* ```ts
* const skill = await client.beta.skills.create();
* ```
*/
create(
params: SkillCreateParams | null | undefined = {},
options?: RequestOptions,
): APIPromise<SkillCreateResponse> {
const { betas, ...body } = params ?? {};
return this._client.post(
'/v1/skills?beta=true',
multipartFormRequestOptions(
{
body,
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'skills-2025-10-02'].toString() },
options?.headers,
]),
},
this._client,
false,
),
);
}
/**
* Get Skill
*
* @example
* ```ts
* const skill = await client.beta.skills.retrieve('skill_id');
* ```
*/
retrieve(
skillID: string,
params: SkillRetrieveParams | null | undefined = {},
options?: RequestOptions,
): APIPromise<SkillRetrieveResponse> {
const { betas } = params ?? {};
return this._client.get(path`/v1/skills/${skillID}?beta=true`, {
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'skills-2025-10-02'].toString() },
options?.headers,
]),
});
}
/**
* List Skills
*
* @example
* ```ts
* // Automatically fetches more pages as needed.
* for await (const skillListResponse of client.beta.skills.list()) {
* // ...
* }
* ```
*/
list(
params: SkillListParams | null | undefined = {},
options?: RequestOptions,
): PagePromise<SkillListResponsesPageCursor, SkillListResponse> {
const { betas, ...query } = params ?? {};
return this._client.getAPIList('/v1/skills?beta=true', PageCursor<SkillListResponse>, {
query,
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'skills-2025-10-02'].toString() },
options?.headers,
]),
});
}
/**
* Delete Skill
*
* @example
* ```ts
* const skill = await client.beta.skills.delete('skill_id');
* ```
*/
delete(
skillID: string,
params: SkillDeleteParams | null | undefined = {},
options?: RequestOptions,
): APIPromise<SkillDeleteResponse> {
const { betas } = params ?? {};
return this._client.delete(path`/v1/skills/${skillID}?beta=true`, {
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'skills-2025-10-02'].toString() },
options?.headers,
]),
});
}
}
export type SkillListResponsesPageCursor = PageCursor<SkillListResponse>;
export interface SkillCreateResponse {
/**
* Unique identifier for the skill.
*
* The format and length of IDs may change over time.
*/
id: string;
/**
* ISO 8601 timestamp of when the skill was created.
*/
created_at: string;
/**
* Display title for the skill.
*
* This is a human-readable label that is not included in the prompt sent to the
* model.
*/
display_title: string | null;
/**
* The latest version identifier for the skill.
*
* This represents the most recent version of the skill that has been created.
*/
latest_version: string | null;
/**
* Source of the skill.
*
* This may be one of the following values:
*
* - `"custom"`: the skill was created by a user
* - `"anthropic"`: the skill was created by Anthropic
*/
source: string;
/**
* Object type.
*
* For Skills, this is always `"skill"`.
*/
type: string;
/**
* ISO 8601 timestamp of when the skill was last updated.
*/
updated_at: string;
}
export interface SkillRetrieveResponse {
/**
* Unique identifier for the skill.
*
* The format and length of IDs may change over time.
*/
id: string;
/**
* ISO 8601 timestamp of when the skill was created.
*/
created_at: string;
/**
* Display title for the skill.
*
* This is a human-readable label that is not included in the prompt sent to the
* model.
*/
display_title: string | null;
/**
* The latest version identifier for the skill.
*
* This represents the most recent version of the skill that has been created.
*/
latest_version: string | null;
/**
* Source of the skill.
*
* This may be one of the following values:
*
* - `"custom"`: the skill was created by a user
* - `"anthropic"`: the skill was created by Anthropic
*/
source: string;
/**
* Object type.
*
* For Skills, this is always `"skill"`.
*/
type: string;
/**
* ISO 8601 timestamp of when the skill was last updated.
*/
updated_at: string;
}
export interface SkillListResponse {
/**
* Unique identifier for the skill.
*
* The format and length of IDs may change over time.
*/
id: string;
/**
* ISO 8601 timestamp of when the skill was created.
*/
created_at: string;
/**
* Display title for the skill.
*
* This is a human-readable label that is not included in the prompt sent to the
* model.
*/
display_title: string | null;
/**
* The latest version identifier for the skill.
*
* This represents the most recent version of the skill that has been created.
*/
latest_version: string | null;
/**
* Source of the skill.
*
* This may be one of the following values:
*
* - `"custom"`: the skill was created by a user
* - `"anthropic"`: the skill was created by Anthropic
*/
source: string;
/**
* Object type.
*
* For Skills, this is always `"skill"`.
*/
type: string;
/**
* ISO 8601 timestamp of when the skill was last updated.
*/
updated_at: string;
}
export interface SkillDeleteResponse {
/**
* Unique identifier for the skill.
*
* The format and length of IDs may change over time.
*/
id: string;
/**
* Deleted object type.
*
* For Skills, this is always `"skill_deleted"`.
*/
type: string;
}
export interface SkillCreateParams {
/**
* Body param: Display title for the skill.
*
* This is a human-readable label that is not included in the prompt sent to the
* model.
*/
display_title?: string | null;
/**
* Body param: Files to upload for the skill.
*
* All files must be in the same top-level directory and must include a SKILL.md
* file at the root of that directory.
*/
files?: Array<Uploadable> | null;
/**
* Header param: Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export interface SkillRetrieveParams {
/**
* Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export interface SkillListParams extends PageCursorParams {
/**
* Query param: Filter skills by source.
*
* If provided, only skills from the specified source will be returned:
*
* - `"custom"`: only return user-created skills
* - `"anthropic"`: only return Anthropic-created skills
*/
source?: string | null;
/**
* Header param: Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export interface SkillDeleteParams {
/**
* Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
Skills.Versions = Versions;
export declare namespace Skills {
export {
type SkillCreateResponse as SkillCreateResponse,
type SkillRetrieveResponse as SkillRetrieveResponse,
type SkillListResponse as SkillListResponse,
type SkillDeleteResponse as SkillDeleteResponse,
type SkillListResponsesPageCursor as SkillListResponsesPageCursor,
type SkillCreateParams as SkillCreateParams,
type SkillRetrieveParams as SkillRetrieveParams,
type SkillListParams as SkillListParams,
type SkillDeleteParams as SkillDeleteParams,
};
export {
Versions as Versions,
type VersionCreateResponse as VersionCreateResponse,
type VersionRetrieveResponse as VersionRetrieveResponse,
type VersionListResponse as VersionListResponse,
type VersionDeleteResponse as VersionDeleteResponse,
type VersionListResponsesPageCursor as VersionListResponsesPageCursor,
type VersionCreateParams as VersionCreateParams,
type VersionRetrieveParams as VersionRetrieveParams,
type VersionListParams as VersionListParams,
type VersionDeleteParams as VersionDeleteParams,
};
}

View File

@@ -0,0 +1,374 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from '../../../core/resource';
import * as BetaAPI from '../beta';
import { APIPromise } from '../../../core/api-promise';
import { PageCursor, type PageCursorParams, PagePromise } from '../../../core/pagination';
import { type Uploadable } from '../../../core/uploads';
import { buildHeaders } from '../../../internal/headers';
import { RequestOptions } from '../../../internal/request-options';
import { multipartFormRequestOptions } from '../../../internal/uploads';
import { path } from '../../../internal/utils/path';
export class Versions extends APIResource {
/**
* Create Skill Version
*
* @example
* ```ts
* const version = await client.beta.skills.versions.create(
* 'skill_id',
* );
* ```
*/
create(
skillID: string,
params: VersionCreateParams | null | undefined = {},
options?: RequestOptions,
): APIPromise<VersionCreateResponse> {
const { betas, ...body } = params ?? {};
return this._client.post(
path`/v1/skills/${skillID}/versions?beta=true`,
multipartFormRequestOptions(
{
body,
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'skills-2025-10-02'].toString() },
options?.headers,
]),
},
this._client,
),
);
}
/**
* Get Skill Version
*
* @example
* ```ts
* const version = await client.beta.skills.versions.retrieve(
* 'version',
* { skill_id: 'skill_id' },
* );
* ```
*/
retrieve(
version: string,
params: VersionRetrieveParams,
options?: RequestOptions,
): APIPromise<VersionRetrieveResponse> {
const { skill_id, betas } = params;
return this._client.get(path`/v1/skills/${skill_id}/versions/${version}?beta=true`, {
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'skills-2025-10-02'].toString() },
options?.headers,
]),
});
}
/**
* List Skill Versions
*
* @example
* ```ts
* // Automatically fetches more pages as needed.
* for await (const versionListResponse of client.beta.skills.versions.list(
* 'skill_id',
* )) {
* // ...
* }
* ```
*/
list(
skillID: string,
params: VersionListParams | null | undefined = {},
options?: RequestOptions,
): PagePromise<VersionListResponsesPageCursor, VersionListResponse> {
const { betas, ...query } = params ?? {};
return this._client.getAPIList(
path`/v1/skills/${skillID}/versions?beta=true`,
PageCursor<VersionListResponse>,
{
query,
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'skills-2025-10-02'].toString() },
options?.headers,
]),
},
);
}
/**
* Delete Skill Version
*
* @example
* ```ts
* const version = await client.beta.skills.versions.delete(
* 'version',
* { skill_id: 'skill_id' },
* );
* ```
*/
delete(
version: string,
params: VersionDeleteParams,
options?: RequestOptions,
): APIPromise<VersionDeleteResponse> {
const { skill_id, betas } = params;
return this._client.delete(path`/v1/skills/${skill_id}/versions/${version}?beta=true`, {
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'skills-2025-10-02'].toString() },
options?.headers,
]),
});
}
}
export type VersionListResponsesPageCursor = PageCursor<VersionListResponse>;
export interface VersionCreateResponse {
/**
* Unique identifier for the skill version.
*
* The format and length of IDs may change over time.
*/
id: string;
/**
* ISO 8601 timestamp of when the skill version was created.
*/
created_at: string;
/**
* Description of the skill version.
*
* This is extracted from the SKILL.md file in the skill upload.
*/
description: string;
/**
* Directory name of the skill version.
*
* This is the top-level directory name that was extracted from the uploaded files.
*/
directory: string;
/**
* Human-readable name of the skill version.
*
* This is extracted from the SKILL.md file in the skill upload.
*/
name: string;
/**
* Identifier for the skill that this version belongs to.
*/
skill_id: string;
/**
* Object type.
*
* For Skill Versions, this is always `"skill_version"`.
*/
type: string;
/**
* Version identifier for the skill.
*
* Each version is identified by a Unix epoch timestamp (e.g., "1759178010641129").
*/
version: string;
}
export interface VersionRetrieveResponse {
/**
* Unique identifier for the skill version.
*
* The format and length of IDs may change over time.
*/
id: string;
/**
* ISO 8601 timestamp of when the skill version was created.
*/
created_at: string;
/**
* Description of the skill version.
*
* This is extracted from the SKILL.md file in the skill upload.
*/
description: string;
/**
* Directory name of the skill version.
*
* This is the top-level directory name that was extracted from the uploaded files.
*/
directory: string;
/**
* Human-readable name of the skill version.
*
* This is extracted from the SKILL.md file in the skill upload.
*/
name: string;
/**
* Identifier for the skill that this version belongs to.
*/
skill_id: string;
/**
* Object type.
*
* For Skill Versions, this is always `"skill_version"`.
*/
type: string;
/**
* Version identifier for the skill.
*
* Each version is identified by a Unix epoch timestamp (e.g., "1759178010641129").
*/
version: string;
}
export interface VersionListResponse {
/**
* Unique identifier for the skill version.
*
* The format and length of IDs may change over time.
*/
id: string;
/**
* ISO 8601 timestamp of when the skill version was created.
*/
created_at: string;
/**
* Description of the skill version.
*
* This is extracted from the SKILL.md file in the skill upload.
*/
description: string;
/**
* Directory name of the skill version.
*
* This is the top-level directory name that was extracted from the uploaded files.
*/
directory: string;
/**
* Human-readable name of the skill version.
*
* This is extracted from the SKILL.md file in the skill upload.
*/
name: string;
/**
* Identifier for the skill that this version belongs to.
*/
skill_id: string;
/**
* Object type.
*
* For Skill Versions, this is always `"skill_version"`.
*/
type: string;
/**
* Version identifier for the skill.
*
* Each version is identified by a Unix epoch timestamp (e.g., "1759178010641129").
*/
version: string;
}
export interface VersionDeleteResponse {
/**
* Version identifier for the skill.
*
* Each version is identified by a Unix epoch timestamp (e.g., "1759178010641129").
*/
id: string;
/**
* Deleted object type.
*
* For Skill Versions, this is always `"skill_version_deleted"`.
*/
type: string;
}
export interface VersionCreateParams {
/**
* Body param: Files to upload for the skill.
*
* All files must be in the same top-level directory and must include a SKILL.md
* file at the root of that directory.
*/
files?: Array<Uploadable> | null;
/**
* Header param: Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export interface VersionRetrieveParams {
/**
* Path param: Unique identifier for the skill.
*
* The format and length of IDs may change over time.
*/
skill_id: string;
/**
* Header param: Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export interface VersionListParams extends PageCursorParams {
/**
* Header param: Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export interface VersionDeleteParams {
/**
* Path param: Unique identifier for the skill.
*
* The format and length of IDs may change over time.
*/
skill_id: string;
/**
* Header param: Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export declare namespace Versions {
export {
type VersionCreateResponse as VersionCreateResponse,
type VersionRetrieveResponse as VersionRetrieveResponse,
type VersionListResponse as VersionListResponse,
type VersionDeleteResponse as VersionDeleteResponse,
type VersionListResponsesPageCursor as VersionListResponsesPageCursor,
type VersionCreateParams as VersionCreateParams,
type VersionRetrieveParams as VersionRetrieveParams,
type VersionListParams as VersionListParams,
type VersionDeleteParams as VersionDeleteParams,
};
}

View File

@@ -0,0 +1,230 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from '../core/resource';
import * as CompletionsAPI from './completions';
import * as BetaAPI from './beta/beta';
import * as MessagesAPI from './messages/messages';
import { APIPromise } from '../core/api-promise';
import { Stream } from '../core/streaming';
import { buildHeaders } from '../internal/headers';
import { RequestOptions } from '../internal/request-options';
export class Completions extends APIResource {
/**
* [Legacy] Create a Text Completion.
*
* The Text Completions API is a legacy API. We recommend using the
* [Messages API](https://docs.claude.com/en/api/messages) going forward.
*
* Future models and features will not be compatible with Text Completions. See our
* [migration guide](https://docs.claude.com/en/api/migrating-from-text-completions-to-messages)
* for guidance in migrating from Text Completions to Messages.
*
* @example
* ```ts
* const completion = await client.completions.create({
* max_tokens_to_sample: 256,
* model: 'claude-opus-4-6',
* prompt: '\n\nHuman: Hello, world!\n\nAssistant:',
* });
* ```
*/
create(params: CompletionCreateParamsNonStreaming, options?: RequestOptions): APIPromise<Completion>;
create(params: CompletionCreateParamsStreaming, options?: RequestOptions): APIPromise<Stream<Completion>>;
create(
params: CompletionCreateParamsBase,
options?: RequestOptions,
): APIPromise<Stream<Completion> | Completion>;
create(
params: CompletionCreateParams,
options?: RequestOptions,
): APIPromise<Completion> | APIPromise<Stream<Completion>> {
const { betas, ...body } = params;
return this._client.post('/v1/complete', {
body,
timeout: (this._client as any)._options.timeout ?? 600000,
...options,
headers: buildHeaders([
{ ...(betas?.toString() != null ? { 'anthropic-beta': betas?.toString() } : undefined) },
options?.headers,
]),
stream: params.stream ?? false,
}) as APIPromise<Completion> | APIPromise<Stream<Completion>>;
}
}
export interface Completion {
/**
* Unique object identifier.
*
* The format and length of IDs may change over time.
*/
id: string;
/**
* The resulting completion up to and excluding the stop sequences.
*/
completion: string;
/**
* The model that will complete your prompt.\n\nSee
* [models](https://docs.anthropic.com/en/docs/models-overview) for additional
* details and options.
*/
model: MessagesAPI.Model;
/**
* The reason that we stopped.
*
* This may be one the following values:
*
* - `"stop_sequence"`: we reached a stop sequence — either provided by you via the
* `stop_sequences` parameter, or a stop sequence built into the model
* - `"max_tokens"`: we exceeded `max_tokens_to_sample` or the model's maximum
*/
stop_reason: string | null;
/**
* Object type.
*
* For Text Completions, this is always `"completion"`.
*/
type: 'completion';
}
export type CompletionCreateParams = CompletionCreateParamsNonStreaming | CompletionCreateParamsStreaming;
export interface CompletionCreateParamsBase {
/**
* Body param: The maximum number of tokens to generate before stopping.
*
* Note that our models may stop _before_ reaching this maximum. This parameter
* only specifies the absolute maximum number of tokens to generate.
*/
max_tokens_to_sample: number;
/**
* Body param: The model that will complete your prompt.\n\nSee
* [models](https://docs.anthropic.com/en/docs/models-overview) for additional
* details and options.
*/
model: MessagesAPI.Model;
/**
* Body param: The prompt that you want Claude to complete.
*
* For proper response generation you will need to format your prompt using
* alternating `\n\nHuman:` and `\n\nAssistant:` conversational turns. For example:
*
* ```
* "\n\nHuman: {userQuestion}\n\nAssistant:"
* ```
*
* See [prompt validation](https://docs.claude.com/en/api/prompt-validation) and
* our guide to [prompt design](https://docs.claude.com/en/docs/intro-to-prompting)
* for more details.
*/
prompt: string;
/**
* Body param: An object describing metadata about the request.
*/
metadata?: MessagesAPI.Metadata;
/**
* Body param: Sequences that will cause the model to stop generating.
*
* Our models stop on `"\n\nHuman:"`, and may include additional built-in stop
* sequences in the future. By providing the stop_sequences parameter, you may
* include additional strings that will cause the model to stop generating.
*/
stop_sequences?: Array<string>;
/**
* Body param: Whether to incrementally stream the response using server-sent
* events.
*
* See [streaming](https://docs.claude.com/en/api/streaming) for details.
*/
stream?: boolean;
/**
* Body param: Amount of randomness injected into the response.
*
* Defaults to `1.0`. Ranges from `0.0` to `1.0`. Use `temperature` closer to `0.0`
* for analytical / multiple choice, and closer to `1.0` for creative and
* generative tasks.
*
* Note that even with `temperature` of `0.0`, the results will not be fully
* deterministic.
*/
temperature?: number;
/**
* Body param: Only sample from the top K options for each subsequent token.
*
* Used to remove "long tail" low probability responses.
* [Learn more technical details here](https://towardsdatascience.com/how-to-sample-from-language-models-682bceb97277).
*
* Recommended for advanced use cases only. You usually only need to use
* `temperature`.
*/
top_k?: number;
/**
* Body param: Use nucleus sampling.
*
* In nucleus sampling, we compute the cumulative distribution over all the options
* for each subsequent token in decreasing probability order and cut it off once it
* reaches a particular probability specified by `top_p`. You should either alter
* `temperature` or `top_p`, but not both.
*
* Recommended for advanced use cases only. You usually only need to use
* `temperature`.
*/
top_p?: number;
/**
* Header param: Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export namespace CompletionCreateParams {
/**
* @deprecated use `Anthropic.Messages.Metadata` instead
*/
export type Metadata = MessagesAPI.Metadata;
export type CompletionCreateParamsNonStreaming = CompletionsAPI.CompletionCreateParamsNonStreaming;
export type CompletionCreateParamsStreaming = CompletionsAPI.CompletionCreateParamsStreaming;
}
export interface CompletionCreateParamsNonStreaming extends CompletionCreateParamsBase {
/**
* Body param: Whether to incrementally stream the response using server-sent
* events.
*
* See [streaming](https://docs.claude.com/en/api/streaming) for details.
*/
stream?: false;
}
export interface CompletionCreateParamsStreaming extends CompletionCreateParamsBase {
/**
* Body param: Whether to incrementally stream the response using server-sent
* events.
*
* See [streaming](https://docs.claude.com/en/api/streaming) for details.
*/
stream: true;
}
export declare namespace Completions {
export {
type Completion as Completion,
type CompletionCreateParams as CompletionCreateParams,
type CompletionCreateParamsNonStreaming as CompletionCreateParamsNonStreaming,
type CompletionCreateParamsStreaming as CompletionCreateParamsStreaming,
};
}

View File

@@ -0,0 +1,205 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export * from './shared';
export {
Beta,
type AnthropicBeta,
type BetaAPIError,
type BetaAuthenticationError,
type BetaBillingError,
type BetaError,
type BetaErrorResponse,
type BetaGatewayTimeoutError,
type BetaInvalidRequestError,
type BetaNotFoundError,
type BetaOverloadedError,
type BetaPermissionError,
type BetaRateLimitError,
} from './beta/beta';
export {
Completions,
type Completion,
type CompletionCreateParams,
type CompletionCreateParamsNonStreaming,
type CompletionCreateParamsStreaming,
} from './completions';
export {
Messages,
type Base64ImageSource,
type Base64PDFSource,
type BashCodeExecutionOutputBlock,
type BashCodeExecutionOutputBlockParam,
type BashCodeExecutionResultBlock,
type BashCodeExecutionResultBlockParam,
type BashCodeExecutionToolResultBlock,
type BashCodeExecutionToolResultBlockParam,
type BashCodeExecutionToolResultError,
type BashCodeExecutionToolResultErrorCode,
type BashCodeExecutionToolResultErrorParam,
type CacheControlEphemeral,
type CacheCreation,
type CitationCharLocation,
type CitationCharLocationParam,
type CitationContentBlockLocation,
type CitationContentBlockLocationParam,
type CitationPageLocation,
type CitationPageLocationParam,
type CitationSearchResultLocationParam,
type CitationWebSearchResultLocationParam,
type CitationsConfig,
type CitationsConfigParam,
type CitationsDelta,
type CitationsSearchResultLocation,
type CitationsWebSearchResultLocation,
type CodeExecutionOutputBlock,
type CodeExecutionOutputBlockParam,
type CodeExecutionResultBlock,
type CodeExecutionResultBlockParam,
type CodeExecutionTool20250522,
type CodeExecutionTool20250825,
type CodeExecutionTool20260120,
type CodeExecutionToolResultBlock,
type CodeExecutionToolResultBlockContent,
type CodeExecutionToolResultBlockParam,
type CodeExecutionToolResultBlockParamContent,
type CodeExecutionToolResultError,
type CodeExecutionToolResultErrorCode,
type CodeExecutionToolResultErrorParam,
type Container,
type ContainerUploadBlock,
type ContainerUploadBlockParam,
type ContentBlock,
type ContentBlockParam,
type ContentBlockStartEvent,
type ContentBlockStopEvent,
type ContentBlockSource,
type ContentBlockSourceContent,
type DirectCaller,
type DocumentBlock,
type DocumentBlockParam,
type EncryptedCodeExecutionResultBlock,
type EncryptedCodeExecutionResultBlockParam,
type ImageBlockParam,
type InputJSONDelta,
type JSONOutputFormat,
type MemoryTool20250818,
type Message,
type MessageCountTokensTool,
type MessageDeltaEvent,
type MessageDeltaUsage,
type MessageParam,
type MessageStreamParams,
type MessageTokensCount,
type Metadata,
type Model,
type OutputConfig,
type PlainTextSource,
type RawContentBlockDelta,
type RawContentBlockDeltaEvent,
type RawContentBlockStartEvent,
type RawContentBlockStopEvent,
type RawMessageDeltaEvent,
type RawMessageStartEvent,
type RawMessageStopEvent,
type RawMessageStreamEvent,
type RedactedThinkingBlock,
type RedactedThinkingBlockParam,
type SearchResultBlockParam,
type ServerToolCaller,
type ServerToolCaller20260120,
type ServerToolUsage,
type ServerToolUseBlock,
type ServerToolUseBlockParam,
type SignatureDelta,
type StopReason,
type TextBlock,
type TextBlockParam,
type TextCitation,
type TextCitationParam,
type TextDelta,
type TextEditorCodeExecutionCreateResultBlock,
type TextEditorCodeExecutionCreateResultBlockParam,
type TextEditorCodeExecutionStrReplaceResultBlock,
type TextEditorCodeExecutionStrReplaceResultBlockParam,
type TextEditorCodeExecutionToolResultBlock,
type TextEditorCodeExecutionToolResultBlockParam,
type TextEditorCodeExecutionToolResultError,
type TextEditorCodeExecutionToolResultErrorCode,
type TextEditorCodeExecutionToolResultErrorParam,
type TextEditorCodeExecutionViewResultBlock,
type TextEditorCodeExecutionViewResultBlockParam,
type ThinkingBlock,
type ThinkingBlockParam,
type ThinkingConfigAdaptive,
type ThinkingConfigDisabled,
type ThinkingConfigEnabled,
type ThinkingConfigParam,
type ThinkingDelta,
type Tool,
type ToolBash20250124,
type ToolChoice,
type ToolChoiceAny,
type ToolChoiceAuto,
type ToolChoiceNone,
type ToolChoiceTool,
type ToolReferenceBlock,
type ToolReferenceBlockParam,
type ToolResultBlockParam,
type ToolSearchToolBm25_20251119,
type ToolSearchToolRegex20251119,
type ToolSearchToolResultBlock,
type ToolSearchToolResultBlockParam,
type ToolSearchToolResultError,
type ToolSearchToolResultErrorCode,
type ToolSearchToolResultErrorParam,
type ToolSearchToolSearchResultBlock,
type ToolSearchToolSearchResultBlockParam,
type ToolTextEditor20250124,
type ToolTextEditor20250429,
type ToolTextEditor20250728,
type ToolUnion,
type ToolUseBlock,
type ToolUseBlockParam,
type URLImageSource,
type URLPDFSource,
type Usage,
type UserLocation,
type WebFetchBlock,
type WebFetchBlockParam,
type WebFetchTool20250910,
type WebFetchTool20260209,
type WebFetchTool20260309,
type WebFetchToolResultBlock,
type WebFetchToolResultBlockParam,
type WebFetchToolResultErrorBlock,
type WebFetchToolResultErrorBlockParam,
type WebFetchToolResultErrorCode,
type WebSearchResultBlock,
type WebSearchResultBlockParam,
type WebSearchTool20250305,
type WebSearchTool20260209,
type WebSearchToolRequestError,
type WebSearchToolResultBlock,
type WebSearchToolResultBlockContent,
type WebSearchToolResultBlockParam,
type WebSearchToolResultBlockParamContent,
type WebSearchToolResultError,
type WebSearchToolResultErrorCode,
type MessageCreateParams,
type MessageCreateParamsNonStreaming,
type MessageCreateParamsStreaming,
type MessageCountTokensParams,
} from './messages/messages';
export {
Models,
type CapabilitySupport,
type ContextManagementCapability,
type EffortCapability,
type ModelCapabilities,
type ModelInfo,
type ThinkingCapability,
type ThinkingTypes,
type ModelRetrieveParams,
type ModelListParams,
type ModelInfosPage,
} from './models';

View File

@@ -0,0 +1,3 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export * from './messages/index';

View File

@@ -0,0 +1,396 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from '../../core/resource';
import * as Shared from '../shared';
import * as MessagesAPI from './messages';
import { APIPromise } from '../../core/api-promise';
import { Page, type PageParams, PagePromise } from '../../core/pagination';
import { buildHeaders } from '../../internal/headers';
import { RequestOptions } from '../../internal/request-options';
import { JSONLDecoder } from '../../internal/decoders/jsonl';
import { AnthropicError } from '../../error';
import { path } from '../../internal/utils/path';
export class Batches extends APIResource {
/**
* Send a batch of Message creation requests.
*
* The Message Batches API can be used to process multiple Messages API requests at
* once. Once a Message Batch is created, it begins processing immediately. Batches
* can take up to 24 hours to complete.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const messageBatch = await client.messages.batches.create({
* requests: [
* {
* custom_id: 'my-custom-id-1',
* params: {
* max_tokens: 1024,
* messages: [
* { content: 'Hello, world', role: 'user' },
* ],
* model: 'claude-opus-4-6',
* },
* },
* ],
* });
* ```
*/
create(body: BatchCreateParams, options?: RequestOptions): APIPromise<MessageBatch> {
return this._client.post('/v1/messages/batches', { body, ...options });
}
/**
* This endpoint is idempotent and can be used to poll for Message Batch
* completion. To access the results of a Message Batch, make a request to the
* `results_url` field in the response.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const messageBatch = await client.messages.batches.retrieve(
* 'message_batch_id',
* );
* ```
*/
retrieve(messageBatchID: string, options?: RequestOptions): APIPromise<MessageBatch> {
return this._client.get(path`/v1/messages/batches/${messageBatchID}`, options);
}
/**
* List all Message Batches within a Workspace. Most recently created batches are
* returned first.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* // Automatically fetches more pages as needed.
* for await (const messageBatch of client.messages.batches.list()) {
* // ...
* }
* ```
*/
list(
query: BatchListParams | null | undefined = {},
options?: RequestOptions,
): PagePromise<MessageBatchesPage, MessageBatch> {
return this._client.getAPIList('/v1/messages/batches', Page<MessageBatch>, { query, ...options });
}
/**
* Delete a Message Batch.
*
* Message Batches can only be deleted once they've finished processing. If you'd
* like to delete an in-progress batch, you must first cancel it.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const deletedMessageBatch =
* await client.messages.batches.delete('message_batch_id');
* ```
*/
delete(messageBatchID: string, options?: RequestOptions): APIPromise<DeletedMessageBatch> {
return this._client.delete(path`/v1/messages/batches/${messageBatchID}`, options);
}
/**
* Batches may be canceled any time before processing ends. Once cancellation is
* initiated, the batch enters a `canceling` state, at which time the system may
* complete any in-progress, non-interruptible requests before finalizing
* cancellation.
*
* The number of canceled requests is specified in `request_counts`. To determine
* which requests were canceled, check the individual results within the batch.
* Note that cancellation may not result in any canceled requests if they were
* non-interruptible.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const messageBatch = await client.messages.batches.cancel(
* 'message_batch_id',
* );
* ```
*/
cancel(messageBatchID: string, options?: RequestOptions): APIPromise<MessageBatch> {
return this._client.post(path`/v1/messages/batches/${messageBatchID}/cancel`, options);
}
/**
* Streams the results of a Message Batch as a `.jsonl` file.
*
* Each line in the file is a JSON object containing the result of a single request
* in the Message Batch. Results are not guaranteed to be in the same order as
* requests. Use the `custom_id` field to match results to requests.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const messageBatchIndividualResponse =
* await client.messages.batches.results('message_batch_id');
* ```
*/
async results(
messageBatchID: string,
options?: RequestOptions,
): Promise<JSONLDecoder<MessageBatchIndividualResponse>> {
const batch = await this.retrieve(messageBatchID);
if (!batch.results_url) {
throw new AnthropicError(
`No batch \`results_url\`; Has it finished processing? ${batch.processing_status} - ${batch.id}`,
);
}
return this._client
.get(batch.results_url, {
...options,
headers: buildHeaders([{ Accept: 'application/binary' }, options?.headers]),
stream: true,
__binaryResponse: true,
})
._thenUnwrap((_, props) => JSONLDecoder.fromResponse(props.response, props.controller)) as APIPromise<
JSONLDecoder<MessageBatchIndividualResponse>
>;
}
}
export type MessageBatchesPage = Page<MessageBatch>;
export interface DeletedMessageBatch {
/**
* ID of the Message Batch.
*/
id: string;
/**
* Deleted object type.
*
* For Message Batches, this is always `"message_batch_deleted"`.
*/
type: 'message_batch_deleted';
}
export interface MessageBatch {
/**
* Unique object identifier.
*
* The format and length of IDs may change over time.
*/
id: string;
/**
* RFC 3339 datetime string representing the time at which the Message Batch was
* archived and its results became unavailable.
*/
archived_at: string | null;
/**
* RFC 3339 datetime string representing the time at which cancellation was
* initiated for the Message Batch. Specified only if cancellation was initiated.
*/
cancel_initiated_at: string | null;
/**
* RFC 3339 datetime string representing the time at which the Message Batch was
* created.
*/
created_at: string;
/**
* RFC 3339 datetime string representing the time at which processing for the
* Message Batch ended. Specified only once processing ends.
*
* Processing ends when every request in a Message Batch has either succeeded,
* errored, canceled, or expired.
*/
ended_at: string | null;
/**
* RFC 3339 datetime string representing the time at which the Message Batch will
* expire and end processing, which is 24 hours after creation.
*/
expires_at: string;
/**
* Processing status of the Message Batch.
*/
processing_status: 'in_progress' | 'canceling' | 'ended';
/**
* Tallies requests within the Message Batch, categorized by their status.
*
* Requests start as `processing` and move to one of the other statuses only once
* processing of the entire batch ends. The sum of all values always matches the
* total number of requests in the batch.
*/
request_counts: MessageBatchRequestCounts;
/**
* URL to a `.jsonl` file containing the results of the Message Batch requests.
* Specified only once processing ends.
*
* Results in the file are not guaranteed to be in the same order as requests. Use
* the `custom_id` field to match results to requests.
*/
results_url: string | null;
/**
* Object type.
*
* For Message Batches, this is always `"message_batch"`.
*/
type: 'message_batch';
}
export interface MessageBatchCanceledResult {
type: 'canceled';
}
export interface MessageBatchErroredResult {
error: Shared.ErrorResponse;
type: 'errored';
}
export interface MessageBatchExpiredResult {
type: 'expired';
}
/**
* This is a single line in the response `.jsonl` file and does not represent the
* response as a whole.
*/
export interface MessageBatchIndividualResponse {
/**
* Developer-provided ID created for each request in a Message Batch. Useful for
* matching results to requests, as results may be given out of request order.
*
* Must be unique for each request within the Message Batch.
*/
custom_id: string;
/**
* Processing result for this request.
*
* Contains a Message output if processing was successful, an error response if
* processing failed, or the reason why processing was not attempted, such as
* cancellation or expiration.
*/
result: MessageBatchResult;
}
export interface MessageBatchRequestCounts {
/**
* Number of requests in the Message Batch that have been canceled.
*
* This is zero until processing of the entire Message Batch has ended.
*/
canceled: number;
/**
* Number of requests in the Message Batch that encountered an error.
*
* This is zero until processing of the entire Message Batch has ended.
*/
errored: number;
/**
* Number of requests in the Message Batch that have expired.
*
* This is zero until processing of the entire Message Batch has ended.
*/
expired: number;
/**
* Number of requests in the Message Batch that are processing.
*/
processing: number;
/**
* Number of requests in the Message Batch that have completed successfully.
*
* This is zero until processing of the entire Message Batch has ended.
*/
succeeded: number;
}
/**
* Processing result for this request.
*
* Contains a Message output if processing was successful, an error response if
* processing failed, or the reason why processing was not attempted, such as
* cancellation or expiration.
*/
export type MessageBatchResult =
| MessageBatchSucceededResult
| MessageBatchErroredResult
| MessageBatchCanceledResult
| MessageBatchExpiredResult;
export interface MessageBatchSucceededResult {
message: MessagesAPI.Message;
type: 'succeeded';
}
export interface BatchCreateParams {
/**
* List of requests for prompt completion. Each is an individual request to create
* a Message.
*/
requests: Array<BatchCreateParams.Request>;
}
export namespace BatchCreateParams {
export interface Request {
/**
* Developer-provided ID created for each request in a Message Batch. Useful for
* matching results to requests, as results may be given out of request order.
*
* Must be unique for each request within the Message Batch.
*/
custom_id: string;
/**
* Messages API creation parameters for the individual request.
*
* See the [Messages API reference](https://docs.claude.com/en/api/messages) for
* full documentation on available parameters.
*/
params: MessagesAPI.MessageCreateParamsNonStreaming;
}
}
export interface BatchListParams extends PageParams {}
export declare namespace Batches {
export {
type DeletedMessageBatch as DeletedMessageBatch,
type MessageBatch as MessageBatch,
type MessageBatchCanceledResult as MessageBatchCanceledResult,
type MessageBatchErroredResult as MessageBatchErroredResult,
type MessageBatchExpiredResult as MessageBatchExpiredResult,
type MessageBatchIndividualResponse as MessageBatchIndividualResponse,
type MessageBatchRequestCounts as MessageBatchRequestCounts,
type MessageBatchResult as MessageBatchResult,
type MessageBatchSucceededResult as MessageBatchSucceededResult,
type MessageBatchesPage as MessageBatchesPage,
type BatchCreateParams as BatchCreateParams,
type BatchListParams as BatchListParams,
};
}

View File

@@ -0,0 +1,188 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export {
Batches,
type DeletedMessageBatch,
type MessageBatch,
type MessageBatchCanceledResult,
type MessageBatchErroredResult,
type MessageBatchExpiredResult,
type MessageBatchIndividualResponse,
type MessageBatchRequestCounts,
type MessageBatchResult,
type MessageBatchSucceededResult,
type BatchCreateParams,
type BatchListParams,
type MessageBatchesPage,
} from './batches';
export {
Messages,
type Base64ImageSource,
type Base64PDFSource,
type BashCodeExecutionOutputBlock,
type BashCodeExecutionOutputBlockParam,
type BashCodeExecutionResultBlock,
type BashCodeExecutionResultBlockParam,
type BashCodeExecutionToolResultBlock,
type BashCodeExecutionToolResultBlockParam,
type BashCodeExecutionToolResultError,
type BashCodeExecutionToolResultErrorCode,
type BashCodeExecutionToolResultErrorParam,
type CacheControlEphemeral,
type CacheCreation,
type CitationCharLocation,
type CitationCharLocationParam,
type CitationContentBlockLocation,
type CitationContentBlockLocationParam,
type CitationPageLocation,
type CitationPageLocationParam,
type CitationSearchResultLocationParam,
type CitationWebSearchResultLocationParam,
type CitationsConfig,
type CitationsConfigParam,
type CitationsDelta,
type CitationsSearchResultLocation,
type CitationsWebSearchResultLocation,
type CodeExecutionOutputBlock,
type CodeExecutionOutputBlockParam,
type CodeExecutionResultBlock,
type CodeExecutionResultBlockParam,
type CodeExecutionTool20250522,
type CodeExecutionTool20250825,
type CodeExecutionTool20260120,
type CodeExecutionToolResultBlock,
type CodeExecutionToolResultBlockContent,
type CodeExecutionToolResultBlockParam,
type CodeExecutionToolResultBlockParamContent,
type CodeExecutionToolResultError,
type CodeExecutionToolResultErrorCode,
type CodeExecutionToolResultErrorParam,
type Container,
type ContainerUploadBlock,
type ContainerUploadBlockParam,
type ContentBlock,
type ContentBlockParam,
type ContentBlockStartEvent,
type ContentBlockStopEvent,
type ContentBlockSource,
type ContentBlockSourceContent,
type DirectCaller,
type DocumentBlock,
type DocumentBlockParam,
type EncryptedCodeExecutionResultBlock,
type EncryptedCodeExecutionResultBlockParam,
type ImageBlockParam,
type InputJSONDelta,
type JSONOutputFormat,
type MemoryTool20250818,
type Message,
type MessageCountTokensTool,
type MessageDeltaEvent,
type MessageDeltaUsage,
type MessageParam,
type MessageTokensCount,
type Metadata,
type Model,
type OutputConfig,
type PlainTextSource,
type RawContentBlockDelta,
type RawContentBlockDeltaEvent,
type RawContentBlockStartEvent,
type RawContentBlockStopEvent,
type RawMessageDeltaEvent,
type RawMessageStartEvent,
type RawMessageStopEvent,
type RawMessageStreamEvent,
type RedactedThinkingBlock,
type RedactedThinkingBlockParam,
type SearchResultBlockParam,
type ServerToolCaller,
type ServerToolCaller20260120,
type ServerToolUsage,
type ServerToolUseBlock,
type ServerToolUseBlockParam,
type SignatureDelta,
type StopReason,
type TextBlock,
type TextBlockParam,
type TextCitation,
type TextCitationParam,
type TextDelta,
type TextEditorCodeExecutionCreateResultBlock,
type TextEditorCodeExecutionCreateResultBlockParam,
type TextEditorCodeExecutionStrReplaceResultBlock,
type TextEditorCodeExecutionStrReplaceResultBlockParam,
type TextEditorCodeExecutionToolResultBlock,
type TextEditorCodeExecutionToolResultBlockParam,
type TextEditorCodeExecutionToolResultError,
type TextEditorCodeExecutionToolResultErrorCode,
type TextEditorCodeExecutionToolResultErrorParam,
type TextEditorCodeExecutionViewResultBlock,
type TextEditorCodeExecutionViewResultBlockParam,
type ThinkingBlock,
type ThinkingBlockParam,
type ThinkingConfigAdaptive,
type ThinkingConfigDisabled,
type ThinkingConfigEnabled,
type ThinkingConfigParam,
type ThinkingDelta,
type Tool,
type ToolBash20250124,
type ToolChoice,
type ToolChoiceAny,
type ToolChoiceAuto,
type ToolChoiceNone,
type ToolChoiceTool,
type ToolReferenceBlock,
type ToolReferenceBlockParam,
type ToolResultBlockParam,
type ToolSearchToolBm25_20251119,
type ToolSearchToolRegex20251119,
type ToolSearchToolResultBlock,
type ToolSearchToolResultBlockParam,
type ToolSearchToolResultError,
type ToolSearchToolResultErrorCode,
type ToolSearchToolResultErrorParam,
type ToolSearchToolSearchResultBlock,
type ToolSearchToolSearchResultBlockParam,
type ToolTextEditor20250124,
type ToolTextEditor20250429,
type ToolTextEditor20250728,
type ToolUnion,
type ToolUseBlock,
type ToolUseBlockParam,
type URLImageSource,
type URLPDFSource,
type Usage,
type UserLocation,
type WebFetchBlock,
type WebFetchBlockParam,
type WebFetchTool20250910,
type WebFetchTool20260209,
type WebFetchTool20260309,
type WebFetchToolResultBlock,
type WebFetchToolResultBlockParam,
type WebFetchToolResultErrorBlock,
type WebFetchToolResultErrorBlockParam,
type WebFetchToolResultErrorCode,
type WebSearchResultBlock,
type WebSearchResultBlockParam,
type WebSearchTool20250305,
type WebSearchTool20260209,
type WebSearchToolRequestError,
type WebSearchToolResultBlock,
type WebSearchToolResultBlockContent,
type WebSearchToolResultBlockParam,
type WebSearchToolResultBlockParamContent,
type WebSearchToolResultError,
type WebSearchToolResultErrorCode,
type MessageStreamEvent,
type MessageStartEvent,
type MessageStopEvent,
type ContentBlockDeltaEvent,
type MessageCreateParams,
type MessageCreateParamsBase,
type MessageCreateParamsNonStreaming,
type MessageCreateParamsStreaming,
type MessageCountTokensParams,
} from './messages';

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,269 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from '../core/resource';
import * as BetaAPI from './beta/beta';
import { APIPromise } from '../core/api-promise';
import { Page, type PageParams, PagePromise } from '../core/pagination';
import { buildHeaders } from '../internal/headers';
import { RequestOptions } from '../internal/request-options';
import { path } from '../internal/utils/path';
export class Models extends APIResource {
/**
* Get a specific model.
*
* The Models API response can be used to determine information about a specific
* model or resolve a model alias to a model ID.
*/
retrieve(
modelID: string,
params: ModelRetrieveParams | null | undefined = {},
options?: RequestOptions,
): APIPromise<ModelInfo> {
const { betas } = params ?? {};
return this._client.get(path`/v1/models/${modelID}`, {
...options,
headers: buildHeaders([
{ ...(betas?.toString() != null ? { 'anthropic-beta': betas?.toString() } : undefined) },
options?.headers,
]),
});
}
/**
* List available models.
*
* The Models API response can be used to determine which models are available for
* use in the API. More recently released models are listed first.
*/
list(
params: ModelListParams | null | undefined = {},
options?: RequestOptions,
): PagePromise<ModelInfosPage, ModelInfo> {
const { betas, ...query } = params ?? {};
return this._client.getAPIList('/v1/models', Page<ModelInfo>, {
query,
...options,
headers: buildHeaders([
{ ...(betas?.toString() != null ? { 'anthropic-beta': betas?.toString() } : undefined) },
options?.headers,
]),
});
}
}
export type ModelInfosPage = Page<ModelInfo>;
/**
* Indicates whether a capability is supported.
*/
export interface CapabilitySupport {
/**
* Whether this capability is supported by the model.
*/
supported: boolean;
}
/**
* Context management capability details.
*/
export interface ContextManagementCapability {
/**
* Indicates whether a capability is supported.
*/
clear_thinking_20251015: CapabilitySupport | null;
/**
* Indicates whether a capability is supported.
*/
clear_tool_uses_20250919: CapabilitySupport | null;
/**
* Indicates whether a capability is supported.
*/
compact_20260112: CapabilitySupport | null;
/**
* Whether this capability is supported by the model.
*/
supported: boolean;
}
/**
* Effort (reasoning_effort) capability details.
*/
export interface EffortCapability {
/**
* Whether the model supports high effort level.
*/
high: CapabilitySupport;
/**
* Whether the model supports low effort level.
*/
low: CapabilitySupport;
/**
* Whether the model supports max effort level.
*/
max: CapabilitySupport;
/**
* Whether the model supports medium effort level.
*/
medium: CapabilitySupport;
/**
* Whether this capability is supported by the model.
*/
supported: boolean;
}
/**
* Model capability information.
*/
export interface ModelCapabilities {
/**
* Whether the model supports the Batch API.
*/
batch: CapabilitySupport;
/**
* Whether the model supports citation generation.
*/
citations: CapabilitySupport;
/**
* Whether the model supports code execution tools.
*/
code_execution: CapabilitySupport;
/**
* Context management support and available strategies.
*/
context_management: ContextManagementCapability;
/**
* Effort (reasoning_effort) support and available levels.
*/
effort: EffortCapability;
/**
* Whether the model accepts image content blocks.
*/
image_input: CapabilitySupport;
/**
* Whether the model accepts PDF content blocks.
*/
pdf_input: CapabilitySupport;
/**
* Whether the model supports structured output / JSON mode / strict tool schemas.
*/
structured_outputs: CapabilitySupport;
/**
* Thinking capability and supported type configurations.
*/
thinking: ThinkingCapability;
}
export interface ModelInfo {
/**
* Unique model identifier.
*/
id: string;
/**
* Model capability information.
*/
capabilities: ModelCapabilities | null;
/**
* RFC 3339 datetime string representing the time at which the model was released.
* May be set to an epoch value if the release date is unknown.
*/
created_at: string;
/**
* A human-readable name for the model.
*/
display_name: string;
/**
* Maximum input context window size in tokens for this model.
*/
max_input_tokens: number | null;
/**
* Maximum value for the `max_tokens` parameter when using this model.
*/
max_tokens: number | null;
/**
* Object type.
*
* For Models, this is always `"model"`.
*/
type: 'model';
}
/**
* Thinking capability details.
*/
export interface ThinkingCapability {
/**
* Whether this capability is supported by the model.
*/
supported: boolean;
/**
* Supported thinking type configurations.
*/
types: ThinkingTypes;
}
/**
* Supported thinking type configurations.
*/
export interface ThinkingTypes {
/**
* Whether the model supports thinking with type 'adaptive' (auto).
*/
adaptive: CapabilitySupport;
/**
* Whether the model supports thinking with type 'enabled'.
*/
enabled: CapabilitySupport;
}
export interface ModelRetrieveParams {
/**
* Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export interface ModelListParams extends PageParams {
/**
* Header param: Optional header to specify the beta version(s) you want to use.
*/
betas?: Array<BetaAPI.AnthropicBeta>;
}
export declare namespace Models {
export {
type CapabilitySupport as CapabilitySupport,
type ContextManagementCapability as ContextManagementCapability,
type EffortCapability as EffortCapability,
type ModelCapabilities as ModelCapabilities,
type ModelInfo as ModelInfo,
type ThinkingCapability as ThinkingCapability,
type ThinkingTypes as ThinkingTypes,
type ModelInfosPage as ModelInfosPage,
type ModelRetrieveParams as ModelRetrieveParams,
type ModelListParams as ModelListParams,
};
}

View File

@@ -0,0 +1,85 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export interface APIErrorObject {
message: string;
type: 'api_error';
}
export interface AuthenticationError {
message: string;
type: 'authentication_error';
}
export interface BillingError {
message: string;
type: 'billing_error';
}
export type ErrorObject =
| InvalidRequestError
| AuthenticationError
| BillingError
| PermissionError
| NotFoundError
| RateLimitError
| GatewayTimeoutError
| APIErrorObject
| OverloadedError;
export interface ErrorResponse {
error: ErrorObject;
request_id: string | null;
type: 'error';
}
export type ErrorType =
| 'invalid_request_error'
| 'authentication_error'
| 'permission_error'
| 'not_found_error'
| 'rate_limit_error'
| 'timeout_error'
| 'overloaded_error'
| 'api_error'
| 'billing_error';
export interface GatewayTimeoutError {
message: string;
type: 'timeout_error';
}
export interface InvalidRequestError {
message: string;
type: 'invalid_request_error';
}
export interface NotFoundError {
message: string;
type: 'not_found_error';
}
export interface OverloadedError {
message: string;
type: 'overloaded_error';
}
export interface PermissionError {
message: string;
type: 'permission_error';
}
export interface RateLimitError {
message: string;
type: 'rate_limit_error';
}

View File

@@ -0,0 +1,3 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export {};

View File

@@ -0,0 +1,2 @@
/** @deprecated Import from ./core/streaming instead */
export * from './core/streaming';

View File

@@ -0,0 +1,392 @@
export { betaMemoryTool } from '../../helpers/beta/memory';
export type { MemoryToolHandlers } from '../../helpers/beta/memory';
import * as fs from 'fs/promises';
import * as path from 'path';
import { randomUUID } from 'crypto';
import type { MemoryToolHandlers } from '../../helpers/beta/memory';
import {
BetaMemoryTool20250818CreateCommand,
BetaMemoryTool20250818DeleteCommand,
BetaMemoryTool20250818InsertCommand,
BetaMemoryTool20250818RenameCommand,
BetaMemoryTool20250818StrReplaceCommand,
BetaMemoryTool20250818ViewCommand,
} from '../../resources/beta';
async function exists(path: string) {
return await fs
.access(path)
.then(() => true)
.catch((err) => {
if (err.code === 'ENOENT') return false;
throw err;
});
}
/**
* Atomically writes content to a file by writing to a temporary file first and then renaming it.
* This ensures the target file is never in a partially written state, preventing data corruption
* if the process crashes or is interrupted during the write operation. The rename operation is
* atomic on most file systems, guaranteeing that readers will only ever see the complete old
* content or the complete new content, never a mix or partial state.
*
* @param targetPath - The path where the file should be written
* @param content - The content to write to the file
*/
async function atomicWriteFile(targetPath: string, content: string): Promise<void> {
const dir = path.dirname(targetPath);
const tempPath = path.join(dir, `.tmp-${process.pid}-${randomUUID()}`);
let handle: fs.FileHandle | undefined;
try {
handle = await fs.open(tempPath, 'wx');
await handle.writeFile(content, 'utf-8');
await handle.sync();
await handle.close();
handle = undefined;
await fs.rename(tempPath, targetPath);
} catch (err) {
if (handle) {
await handle.close().catch(() => {});
}
await fs.unlink(tempPath).catch(() => {});
throw err;
}
}
/**
* Validates that a target path doesn't escape the memory root via symlinks.
*
* Prevents symlink attacks where a malicious symlink inside /memories points
* outside (e.g., /memories/foo -> /etc), which would allow operations like
* creating /memories/foo/passwd to actually write to /etc/passwd.
*
* Walks up from the target path to find the deepest existing ancestor,
* then resolves it to ensure the real path stays within memoryRoot.
*/
async function validateNoSymlinkEscape(targetPath: string, memoryRoot: string): Promise<void> {
const resolvedRoot = await fs.realpath(memoryRoot);
let current = targetPath;
while (true) {
try {
const resolved = await fs.realpath(current);
if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot + path.sep)) {
throw new Error(`Path would escape /memories directory via symlink`);
}
return;
} catch (err: any) {
if (err.code !== 'ENOENT') throw err;
const parent = path.dirname(current);
if (parent === current || current === memoryRoot) {
return;
}
current = parent;
}
}
}
async function readFileContent(fullPath: string, memoryPath: string): Promise<string> {
try {
return await fs.readFile(fullPath, 'utf-8');
} catch (err: any) {
if (err.code === 'ENOENT') {
throw new Error(
`The file ${memoryPath} no longer exists (may have been deleted or renamed concurrently).`,
);
}
throw err;
}
}
function formatFileSize(bytes: number): string {
if (bytes === 0) return '0B';
const k = 1024;
const sizes = ['B', 'K', 'M', 'G'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
const size = bytes / Math.pow(k, i);
return (size % 1 === 0 ? size.toString() : size.toFixed(1)) + sizes[i];
}
const MAX_LINES = 999999;
const LINE_NUMBER_WIDTH = String(MAX_LINES).length;
export class BetaLocalFilesystemMemoryTool implements MemoryToolHandlers {
private basePath: string;
private memoryRoot: string;
constructor(basePath: string = './memory') {
this.basePath = basePath;
this.memoryRoot = path.join(this.basePath, 'memories');
}
static async init(basePath: string = './memory'): Promise<BetaLocalFilesystemMemoryTool> {
const memory = new BetaLocalFilesystemMemoryTool(basePath);
await fs.mkdir(memory.memoryRoot, { recursive: true });
return memory;
}
private async validatePath(memoryPath: string): Promise<string> {
if (!memoryPath.startsWith('/memories')) {
throw new Error(`Path must start with /memories, got: ${memoryPath}`);
}
const relativePath = memoryPath.slice('/memories'.length).replace(/^\//, '');
const fullPath = relativePath ? path.join(this.memoryRoot, relativePath) : this.memoryRoot;
const resolvedPath = path.resolve(fullPath);
const resolvedRoot = path.resolve(this.memoryRoot);
if (resolvedPath !== resolvedRoot && !resolvedPath.startsWith(resolvedRoot + path.sep)) {
throw new Error(`Path ${memoryPath} would escape /memories directory`);
}
await validateNoSymlinkEscape(resolvedPath, this.memoryRoot);
return resolvedPath;
}
async view(command: BetaMemoryTool20250818ViewCommand): Promise<string> {
const fullPath = await this.validatePath(command.path);
let stat;
try {
stat = await fs.stat(fullPath);
} catch (err: any) {
if (err.code === 'ENOENT') {
throw new Error(`The path ${command.path} does not exist. Please provide a valid path.`);
}
throw err;
}
if (stat.isDirectory()) {
const items: Array<{ size: string; path: string }> = [];
const collectItems = async (dirPath: string, relativePath: string, depth: number): Promise<void> => {
if (depth > 2) return;
const dirContents = await fs.readdir(dirPath);
for (const item of dirContents.sort()) {
if (item.startsWith('.') || item === 'node_modules') {
continue;
}
const itemPath = path.join(dirPath, item);
const itemRelativePath = relativePath ? `${relativePath}/${item}` : item;
let itemStat;
try {
itemStat = await fs.stat(itemPath);
} catch {
continue;
}
if (itemStat.isDirectory()) {
items.push({ size: formatFileSize(itemStat.size), path: `${itemRelativePath}/` });
if (depth < 2) {
await collectItems(itemPath, itemRelativePath, depth + 1);
}
} else if (itemStat.isFile()) {
items.push({ size: formatFileSize(itemStat.size), path: itemRelativePath });
}
}
};
await collectItems(fullPath, '', 1);
const header = `Here're the files and directories up to 2 levels deep in ${command.path}, excluding hidden items and node_modules:`;
const dirSize = formatFileSize(stat.size);
const lines = [
`${dirSize}\t${command.path}`,
...items.map((item) => `${item.size}\t${command.path}/${item.path}`),
];
return `${header}\n${lines.join('\n')}`;
} else if (stat.isFile()) {
const content = await readFileContent(fullPath, command.path);
const lines = content.split('\n');
if (lines.length > MAX_LINES) {
throw new Error(
`File ${command.path} has too many lines (${
lines.length
}). Maximum is ${MAX_LINES.toLocaleString()} lines.`,
);
}
let displayLines = lines;
let startNum = 1;
if (command.view_range && command.view_range.length === 2) {
const startLine = Math.max(1, command.view_range[0]!) - 1;
const endLine = command.view_range[1] === -1 ? lines.length : command.view_range[1];
displayLines = lines.slice(startLine, endLine);
startNum = startLine + 1;
}
const numberedLines = displayLines.map(
(line, i) => `${String(i + startNum).padStart(LINE_NUMBER_WIDTH, ' ')}\t${line}`,
);
return `Here's the content of ${command.path} with line numbers:\n${numberedLines.join('\n')}`;
} else {
throw new Error(`Unsupported file type for ${command.path}`);
}
}
async create(command: BetaMemoryTool20250818CreateCommand): Promise<string> {
const fullPath = await this.validatePath(command.path);
await fs.mkdir(path.dirname(fullPath), { recursive: true });
let handle: fs.FileHandle | undefined;
try {
handle = await fs.open(fullPath, 'wx');
await handle.writeFile(command.file_text, 'utf-8');
await handle.sync();
} catch (err: any) {
if (err?.code === 'EEXIST') {
throw new Error(`File ${command.path} already exists`);
}
throw err;
} finally {
await handle?.close().catch(() => {});
}
return `File created successfully at: ${command.path}`;
}
async str_replace(command: BetaMemoryTool20250818StrReplaceCommand): Promise<string> {
const fullPath = await this.validatePath(command.path);
let stat;
try {
stat = await fs.stat(fullPath);
} catch (err: any) {
if (err.code === 'ENOENT') {
throw new Error(`The path ${command.path} does not exist. Please provide a valid path.`);
}
throw err;
}
if (!stat.isFile()) {
throw new Error(`The path ${command.path} is not a file.`);
}
const content = await readFileContent(fullPath, command.path);
const lines = content.split('\n');
const matchingLines: number[] = [];
lines.forEach((line, index) => {
if (line.includes(command.old_str)) {
matchingLines.push(index + 1);
}
});
if (matchingLines.length === 0) {
throw new Error(
`No replacement was performed, old_str \`${command.old_str}\` did not appear verbatim in ${command.path}.`,
);
} else if (matchingLines.length > 1) {
throw new Error(
`No replacement was performed. Multiple occurrences of old_str \`${
command.old_str
}\` in lines: ${matchingLines.join(', ')}. Please ensure it is unique`,
);
}
const newContent = content.replace(command.old_str, command.new_str);
await atomicWriteFile(fullPath, newContent);
const newLines = newContent.split('\n');
const changedLineIndex = matchingLines[0]! - 1;
const contextStart = Math.max(0, changedLineIndex - 2);
const contextEnd = Math.min(newLines.length, changedLineIndex + 3);
const snippet = newLines.slice(contextStart, contextEnd).map((line, i) => {
const lineNum = contextStart + i + 1;
return `${String(lineNum).padStart(LINE_NUMBER_WIDTH, ' ')}\t${line}`;
});
return `The memory file has been edited. Here is the snippet showing the change (with line numbers):\n${snippet.join(
'\n',
)}`;
}
async insert(command: BetaMemoryTool20250818InsertCommand): Promise<string> {
const fullPath = await this.validatePath(command.path);
let stat;
try {
stat = await fs.stat(fullPath);
} catch (err: any) {
if (err.code === 'ENOENT') {
throw new Error(`The path ${command.path} does not exist. Please provide a valid path.`);
}
throw err;
}
if (!stat.isFile()) {
throw new Error(`The path ${command.path} is not a file.`);
}
const content = await readFileContent(fullPath, command.path);
const lines = content.split('\n');
if (command.insert_line < 0 || command.insert_line > lines.length) {
throw new Error(
`Invalid \`insert_line\` parameter: ${command.insert_line}. It should be within the range of lines of the file: [0, ${lines.length}]`,
);
}
lines.splice(command.insert_line, 0, command.insert_text.replace(/\n$/, ''));
await atomicWriteFile(fullPath, lines.join('\n'));
return `The file ${command.path} has been edited.`;
}
async delete(command: BetaMemoryTool20250818DeleteCommand): Promise<string> {
const fullPath = await this.validatePath(command.path);
if (command.path === '/memories') {
throw new Error('Cannot delete the /memories directory itself');
}
try {
await fs.rm(fullPath, { recursive: true, force: false });
} catch (err: any) {
if (err.code === 'ENOENT') {
throw new Error(`The path ${command.path} does not exist`);
}
throw err;
}
return `Successfully deleted ${command.path}`;
}
async rename(command: BetaMemoryTool20250818RenameCommand): Promise<string> {
const oldFullPath = await this.validatePath(command.old_path);
const newFullPath = await this.validatePath(command.new_path);
// POSIX rename() silently overwrites existing files without error,
// so we can't catch this atomically. Best-effort check to warn user.
if (await exists(newFullPath)) {
throw new Error(`The destination ${command.new_path} already exists`);
}
const newDir = path.dirname(newFullPath);
await fs.mkdir(newDir, { recursive: true });
try {
await fs.rename(oldFullPath, newFullPath);
} catch (err: any) {
if (err.code === 'ENOENT') {
throw new Error(`The path ${command.old_path} does not exist`);
}
throw err;
}
return `Successfully renamed ${command.old_path} to ${command.new_path}`;
}
}

View File

@@ -0,0 +1,11 @@
{
// this config is included in the published src directory to prevent TS errors
// from appearing when users go to source, and VSCode opens the source .ts file
// via declaration maps
"include": ["index.ts"],
"compilerOptions": {
"target": "ES2015",
"lib": ["DOM", "DOM.Iterable", "ES2018"],
"moduleResolution": "node"
}
}

View File

@@ -0,0 +1,2 @@
/** @deprecated Import from ./core/uploads instead */
export * from './core/uploads';

View File

@@ -0,0 +1 @@
export const VERSION = '0.81.0'; // x-release-please-version