diff --git a/.stats.yml b/.stats.yml index 239e17b7..c809f63b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ configured_endpoints: 21 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic-fd67aea6883f1ee9e46f31a42d3940f0acb1749e787055bd9b9f278b20fa53ec.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic-75f0573c3d6d79650bcbd8b1b4fcf93ce146d567afeb1061cd4afccf8d1d6799.yml diff --git a/api.md b/api.md index d4a311ad..6d080680 100644 --- a/api.md +++ b/api.md @@ -84,7 +84,7 @@ Methods: - client.messages.batches.list({ ...params }) -> MessageBatchesPage - client.messages.batches.delete(messageBatchId) -> DeletedMessageBatch - client.messages.batches.cancel(messageBatchId) -> MessageBatch -- client.messages.batches.results(messageBatchId) -> Response +- client.messages.batches.results(messageBatchId) -> JSONLDecoder<MessageBatchIndividualResponse> # Models @@ -191,4 +191,4 @@ Methods: - client.beta.messages.batches.list({ ...params }) -> BetaMessageBatchesPage - client.beta.messages.batches.delete(messageBatchId, { ...params }) -> BetaDeletedMessageBatch - client.beta.messages.batches.cancel(messageBatchId, { ...params }) -> BetaMessageBatch -- client.beta.messages.batches.results(messageBatchId, { ...params }) -> Response +- client.beta.messages.batches.results(messageBatchId, { ...params }) -> JSONLDecoder<BetaMessageBatchIndividualResponse> diff --git a/src/internal/decoders/jsonl.ts b/src/internal/decoders/jsonl.ts new file mode 100644 index 00000000..15751255 --- /dev/null +++ b/src/internal/decoders/jsonl.ts @@ -0,0 +1,41 @@ +import { AnthropicError } from '../../error'; +import { ReadableStreamToAsyncIterable } from '../stream-utils'; +import { type Response } from '../../_shims/index'; +import { LineDecoder, type Bytes } from './line'; + +export class JSONLDecoder { + controller: AbortController; + + constructor( + private iterator: AsyncIterableIterator, + controller: AbortController, + ) { + this.controller = controller; + } + + private async *decoder(): AsyncIterator { + const lineDecoder = new LineDecoder(); + for await (const chunk of this.iterator) { + for (const line of lineDecoder.decode(chunk)) { + yield JSON.parse(line); + } + } + + for (const line of lineDecoder.flush()) { + yield JSON.parse(line); + } + } + + [Symbol.asyncIterator](): AsyncIterator { + return this.decoder(); + } + + static fromResponse(response: Response, controller: AbortController): JSONLDecoder { + if (!response.body) { + controller.abort(); + throw new AnthropicError(`Attempted to iterate over a response with no body`); + } + + return new JSONLDecoder(ReadableStreamToAsyncIterable(response.body), controller); + } +} diff --git a/src/resources/beta/messages/batches.ts b/src/resources/beta/messages/batches.ts index a6b5997e..2baa2933 100644 --- a/src/resources/beta/messages/batches.ts +++ b/src/resources/beta/messages/batches.ts @@ -7,7 +7,7 @@ import * as BetaAPI from '../beta'; import * as MessagesAPI from '../../messages/messages'; import * as MessagesMessagesAPI from './messages'; import { Page, type PageParams } from '../../../pagination'; -import { type Response } from '../../../_shims/index'; +import { JSONLDecoder } from '../../../internal/decoders/jsonl'; export class Batches extends APIResource { /** @@ -160,26 +160,31 @@ export class Batches extends APIResource { messageBatchId: string, params?: BatchResultsParams, options?: Core.RequestOptions, - ): Core.APIPromise; - results(messageBatchId: string, options?: Core.RequestOptions): Core.APIPromise; + ): Core.APIPromise>; + results( + messageBatchId: string, + options?: Core.RequestOptions, + ): Core.APIPromise>; results( messageBatchId: string, params: BatchResultsParams | Core.RequestOptions = {}, options?: Core.RequestOptions, - ): Core.APIPromise { + ): Core.APIPromise> { if (isRequestOptions(params)) { return this.results(messageBatchId, {}, params); } const { betas } = params; - return this._client.get(`/v1/messages/batches/${messageBatchId}/results?beta=true`, { - ...options, - headers: { - 'anthropic-beta': [...(betas ?? []), 'message-batches-2024-09-24'].toString(), - Accept: 'application/binary', - ...options?.headers, - }, - __binaryResponse: true, - }); + return this._client + .get(`/v1/messages/batches/${messageBatchId}/results?beta=true`, { + ...options, + headers: { + 'anthropic-beta': [...(betas ?? []), 'message-batches-2024-09-24'].toString(), + Accept: 'application/x-jsonl', + ...options?.headers, + }, + __binaryResponse: true, + }) + ._thenUnwrap((_, props) => JSONLDecoder.fromResponse(props.response, props.controller)); } } diff --git a/src/resources/messages/batches.ts b/src/resources/messages/batches.ts index 3425262a..1ffac7b3 100644 --- a/src/resources/messages/batches.ts +++ b/src/resources/messages/batches.ts @@ -6,7 +6,7 @@ import * as Core from '../../core'; import * as Shared from '../shared'; import * as MessagesAPI from './messages'; import { Page, type PageParams } from '../../pagination'; -import { type Response } from '../../_shims/index'; +import { JSONLDecoder } from '../../internal/decoders/jsonl'; export class Batches extends APIResource { /** @@ -79,12 +79,17 @@ export class Batches extends APIResource { * 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. */ - results(messageBatchId: string, options?: Core.RequestOptions): Core.APIPromise { - return this._client.get(`/v1/messages/batches/${messageBatchId}/results`, { - ...options, - headers: { Accept: 'application/binary', ...options?.headers }, - __binaryResponse: true, - }); + results( + messageBatchId: string, + options?: Core.RequestOptions, + ): Core.APIPromise> { + return this._client + .get(`/v1/messages/batches/${messageBatchId}/results`, { + ...options, + headers: { Accept: 'application/x-jsonl', ...options?.headers }, + __binaryResponse: true, + }) + ._thenUnwrap((_, props) => JSONLDecoder.fromResponse(props.response, props.controller)); } } diff --git a/tests/api-resources/beta/messages/batches.test.ts b/tests/api-resources/beta/messages/batches.test.ts index 595d45b9..a0c1ce2b 100644 --- a/tests/api-resources/beta/messages/batches.test.ts +++ b/tests/api-resources/beta/messages/batches.test.ts @@ -190,14 +190,28 @@ describe('resource batches', () => { ).rejects.toThrow(Anthropic.NotFoundError); }); - test('results: request options instead of params are passed correctly', async () => { + // Prism doesn't support JSONL responses yet + test.skip('results', async () => { + const responsePromise = client.beta.messages.batches.results('message_batch_id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism doesn't support JSONL responses yet + test.skip('results: request options instead of params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( client.beta.messages.batches.results('message_batch_id', { path: '/_stainless_unknown_path' }), ).rejects.toThrow(Anthropic.NotFoundError); }); - test('results: request options and params are passed correctly', async () => { + // Prism doesn't support JSONL responses yet + test.skip('results: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( client.beta.messages.batches.results( diff --git a/tests/api-resources/messages/batches.test.ts b/tests/api-resources/messages/batches.test.ts index 784b3143..fdf10664 100644 --- a/tests/api-resources/messages/batches.test.ts +++ b/tests/api-resources/messages/batches.test.ts @@ -155,7 +155,20 @@ describe('resource batches', () => { ).rejects.toThrow(Anthropic.NotFoundError); }); - test('results: request options instead of params are passed correctly', async () => { + // Prism doesn't support JSONL responses yet + test.skip('results', async () => { + const responsePromise = client.messages.batches.results('message_batch_id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism doesn't support JSONL responses yet + test.skip('results: request options instead of params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( client.messages.batches.results('message_batch_id', { path: '/_stainless_unknown_path' }),