From bea220934b8d11d97e35599dbc68ef3e69bb9523 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 31 Aug 2025 21:55:30 +0000 Subject: [PATCH 1/4] feat(api): update via SDK Studio --- .github/workflows/create-releases.yml | 33 -- .github/workflows/release-doctor.yml | 1 - .stats.yml | 6 +- README.md | 22 +- api.md | 48 -- bin/check-release-environment | 4 - scripts/detect-breaking-changes | 8 +- src/client.ts | 8 - src/core/streaming.ts | 315 ------------ src/internal/decoders/line.ts | 135 ----- src/internal/parse.ts | 14 - src/internal/request-options.ts | 2 - src/resources/beta/beta.ts | 146 +----- src/resources/beta/index.ts | 27 +- src/resources/beta/task-group.ts | 267 +--------- src/resources/beta/task-run.ts | 514 +------------------- src/resources/index.ts | 3 - src/resources/shared.ts | 44 +- src/resources/task-run.ts | 241 ++++----- src/streaming.ts | 2 - tests/api-resources/beta/beta.test.ts | 21 - tests/api-resources/beta/task-group.test.ts | 126 ----- tests/api-resources/beta/task-run.test.ts | 80 --- tests/api-resources/task-run.test.ts | 37 +- tests/internal/decoders/line.test.ts | 128 ----- tests/streaming.test.ts | 219 --------- 26 files changed, 141 insertions(+), 2310 deletions(-) delete mode 100644 .github/workflows/create-releases.yml delete mode 100644 src/core/streaming.ts delete mode 100644 src/internal/decoders/line.ts delete mode 100644 src/streaming.ts delete mode 100644 tests/api-resources/beta/beta.test.ts delete mode 100644 tests/api-resources/beta/task-group.test.ts delete mode 100644 tests/api-resources/beta/task-run.test.ts delete mode 100644 tests/internal/decoders/line.test.ts delete mode 100644 tests/streaming.test.ts diff --git a/.github/workflows/create-releases.yml b/.github/workflows/create-releases.yml deleted file mode 100644 index a517c9c..0000000 --- a/.github/workflows/create-releases.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Create releases -on: - schedule: - - cron: '0 5 * * *' # every day at 5am UTC - push: - branches: - - main - -jobs: - release: - name: release - if: github.ref == 'refs/heads/main' && github.repository == 'parallel-web/parallel-sdk-typescript' - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - uses: stainless-api/trigger-release-please@v1 - id: release - with: - repo: ${{ github.event.repository.full_name }} - stainless-api-key: ${{ secrets.STAINLESS_API_KEY }} - - - name: Set up Node - if: ${{ steps.release.outputs.releases_created }} - uses: actions/setup-node@v3 - with: - node-version: '20' - - - name: Install dependencies - if: ${{ steps.release.outputs.releases_created }} - run: | - yarn install diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index fe84245..6ccfd4d 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -18,4 +18,3 @@ jobs: run: | bash ./bin/check-release-environment env: - STAINLESS_API_KEY: ${{ secrets.STAINLESS_API_KEY }} diff --git a/.stats.yml b/.stats.yml index 57243e7..c703e97 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 12 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/parallel-web%2Fparallel-sdk-1aeb1c81a84999f2d27ca9e86b041d74b892926bed126dc9b0f3cff4d7b26963.yml -openapi_spec_hash: 6280f6c6fb537f7c9ac5cc33ee2e433d +configured_endpoints: 3 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/parallel-web%2Fparallel-sdk-ff0d5939e135b67b3448abf72d8bb0f9a574194337c7c7192453781347a9601d.yml +openapi_spec_hash: f3ce85349af6273a671d3d2781c4c877 config_hash: 284b51e02bda8519b1f21bb67f1809e0 diff --git a/README.md b/README.md index 20d1935..6dc066d 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,7 @@ const client = new Parallel({ apiKey: process.env['PARALLEL_API_KEY'], // This is the default and can be omitted }); -const taskRun = await client.taskRun.create({ - input: 'What was the GDP of France in 2023?', - processor: 'base', -}); +const taskRun = await client.taskRun.create({ input: 'France (2023)', processor: 'processor' }); console.log(taskRun.run_id); ``` @@ -49,10 +46,7 @@ const client = new Parallel({ apiKey: process.env['PARALLEL_API_KEY'], // This is the default and can be omitted }); -const params: Parallel.TaskRunCreateParams = { - input: 'What was the GDP of France in 2023?', - processor: 'base', -}; +const params: Parallel.TaskRunCreateParams = { input: 'France (2023)', processor: 'processor' }; const taskRun: Parallel.TaskRun = await client.taskRun.create(params); ``` @@ -67,7 +61,7 @@ a subclass of `APIError` will be thrown: ```ts const taskRun = await client.taskRun - .create({ input: 'What was the GDP of France in 2023?', processor: 'base' }) + .create({ input: 'France (2023)', processor: 'processor' }) .catch(async (err) => { if (err instanceof Parallel.APIError) { console.log(err.status); // 400 @@ -108,7 +102,7 @@ const client = new Parallel({ }); // Or, configure per-request: -await client.taskRun.create({ input: 'What was the GDP of France in 2023?', processor: 'base' }, { +await client.taskRun.create({ input: 'France (2023)', processor: 'processor' }, { maxRetries: 5, }); ``` @@ -125,7 +119,7 @@ const client = new Parallel({ }); // Override per-request: -await client.taskRun.create({ input: 'What was the GDP of France in 2023?', processor: 'base' }, { +await client.taskRun.create({ input: 'France (2023)', processor: 'processor' }, { timeout: 5 * 1000, }); ``` @@ -148,14 +142,12 @@ Unlike `.asResponse()` this method consumes the body, returning once it is parse ```ts const client = new Parallel(); -const response = await client.taskRun - .create({ input: 'What was the GDP of France in 2023?', processor: 'base' }) - .asResponse(); +const response = await client.taskRun.create({ input: 'France (2023)', processor: 'processor' }).asResponse(); console.log(response.headers.get('X-My-Header')); console.log(response.statusText); // access the underlying Response object const { data: taskRun, response: raw } = await client.taskRun - .create({ input: 'What was the GDP of France in 2023?', processor: 'base' }) + .create({ input: 'France (2023)', processor: 'processor' }) .withResponse(); console.log(raw.headers.get('X-My-Header')); console.log(taskRun.run_id); diff --git a/api.md b/api.md index b41a73e..c20960c 100644 --- a/api.md +++ b/api.md @@ -4,18 +4,13 @@ Types: - ErrorObject - ErrorResponse -- SourcePolicy -- Warning # TaskRun Types: -- AutoSchema - Citation -- FieldBasis - JsonSchema -- RunInput - TaskRun - TaskRunJsonOutput - TaskRunResult @@ -31,49 +26,6 @@ Methods: # Beta -Types: - -- SearchResult -- WebSearchResult - -Methods: - -- client.beta.search({ ...params }) -> SearchResult - ## TaskRun -Types: - -- BetaRunInput -- BetaTaskRunResult -- ErrorEvent -- McpServer -- McpToolCall -- ParallelBeta -- TaskRunEvent -- Webhook -- TaskRunEventsResponse - -Methods: - -- client.beta.taskRun.create({ ...params }) -> TaskRun -- client.beta.taskRun.events(runID) -> TaskRunEventsResponse -- client.beta.taskRun.result(runID, { ...params }) -> BetaTaskRunResult - ## TaskGroup - -Types: - -- TaskGroup -- TaskGroupRunResponse -- TaskGroupStatus -- TaskGroupEventsResponse -- TaskGroupGetRunsResponse - -Methods: - -- client.beta.taskGroup.create({ ...params }) -> TaskGroup -- client.beta.taskGroup.retrieve(taskGroupID) -> TaskGroup -- client.beta.taskGroup.addRuns(taskGroupID, { ...params }) -> TaskGroupRunResponse -- client.beta.taskGroup.events(taskGroupID, { ...params }) -> TaskGroupEventsResponse -- client.beta.taskGroup.getRuns(taskGroupID, { ...params }) -> TaskGroupGetRunsResponse diff --git a/bin/check-release-environment b/bin/check-release-environment index a0e7396..6b43775 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -2,10 +2,6 @@ errors=() -if [ -z "${STAINLESS_API_KEY}" ]; then - errors+=("The STAINLESS_API_KEY secret has not been set. Please contact Stainless for an API key & set it in your organization secrets on GitHub.") -fi - lenErrors=${#errors[@]} if [[ lenErrors -gt 0 ]]; then diff --git a/scripts/detect-breaking-changes b/scripts/detect-breaking-changes index 52c6340..69dba46 100755 --- a/scripts/detect-breaking-changes +++ b/scripts/detect-breaking-changes @@ -6,13 +6,7 @@ cd "$(dirname "$0")/.." echo "==> Detecting breaking changes" -TEST_PATHS=( - tests/api-resources/task-run.test.ts - tests/api-resources/beta/beta.test.ts - tests/api-resources/beta/task-run.test.ts - tests/api-resources/beta/task-group.test.ts - tests/index.test.ts -) +TEST_PATHS=( tests/api-resources/task-run.test.ts tests/index.test.ts ) for PATHSPEC in "${TEST_PATHS[@]}"; do # Try to check out previous versions of the test files diff --git a/src/client.ts b/src/client.ts index 4aa25c1..044fd17 100644 --- a/src/client.ts +++ b/src/client.ts @@ -17,11 +17,8 @@ import * as Uploads from './core/uploads'; import * as API from './resources/index'; import { APIPromise } from './core/api-promise'; import { - AutoSchema, Citation, - FieldBasis, JsonSchema, - RunInput, TaskRun, TaskRunCreateParams, TaskRunJsonOutput, @@ -740,11 +737,8 @@ export declare namespace Parallel { export { type TaskRun as TaskRun, - type AutoSchema as AutoSchema, type Citation as Citation, - type FieldBasis as FieldBasis, type JsonSchema as JsonSchema, - type RunInput as RunInput, type TaskRunJsonOutput as TaskRunJsonOutput, type TaskRunResult as TaskRunResult, type TaskRunTextOutput as TaskRunTextOutput, @@ -758,6 +752,4 @@ export declare namespace Parallel { export type ErrorObject = API.ErrorObject; export type ErrorResponse = API.ErrorResponse; - export type SourcePolicy = API.SourcePolicy; - export type Warning = API.Warning; } diff --git a/src/core/streaming.ts b/src/core/streaming.ts deleted file mode 100644 index 57924d1..0000000 --- a/src/core/streaming.ts +++ /dev/null @@ -1,315 +0,0 @@ -import { ParallelError } 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 { encodeUTF8 } from '../internal/utils/bytes'; -import { loggerFor } from '../internal/utils/log'; -import type { Parallel } from '../client'; - -type Bytes = string | ArrayBuffer | Uint8Array | null | undefined; - -export type ServerSentEvent = { - event: string | null; - data: string; - raw: string[]; -}; - -export class Stream implements AsyncIterable { - controller: AbortController; - #client: Parallel | undefined; - - constructor( - private iterator: () => AsyncIterator, - controller: AbortController, - client?: Parallel, - ) { - this.controller = controller; - this.#client = client; - } - - static fromSSEResponse( - response: Response, - controller: AbortController, - client?: Parallel, - ): Stream { - let consumed = false; - const logger = client ? loggerFor(client) : console; - - async function* iterator(): AsyncIterator { - if (consumed) { - throw new ParallelError('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)) { - try { - yield JSON.parse(sse.data); - } catch (e) { - logger.error(`Could not parse message into JSON:`, sse.data); - logger.error(`From chunk:`, sse.raw); - throw e; - } - } - 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( - readableStream: ReadableStream, - controller: AbortController, - client?: Parallel, - ): Stream { - let consumed = false; - - async function* iterLines(): AsyncGenerator { - const lineDecoder = new LineDecoder(); - - const iter = ReadableStreamToAsyncIterable(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 { - if (consumed) { - throw new ParallelError('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); - } - 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 { - return this.iterator(); - } - - /** - * Splits the stream into two streams which can be - * independently read from at different speeds. - */ - tee(): [Stream, Stream] { - const left: Array>> = []; - const right: Array>> = []; - const iterator = this.iterator(); - - const teeIterator = (queue: Array>>): AsyncIterator => { - 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; - - 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 { - if (!response.body) { - controller.abort(); - if ( - typeof (globalThis as any).navigator !== 'undefined' && - (globalThis as any).navigator.product === 'ReactNative' - ) { - throw new ParallelError( - `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 ParallelError(`Attempted to iterate over a response with no body`); - } - - const sseDecoder = new SSEDecoder(); - const lineDecoder = new LineDecoder(); - - const iter = ReadableStreamToAsyncIterable(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): AsyncGenerator { - 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, '', '']; -} diff --git a/src/internal/decoders/line.ts b/src/internal/decoders/line.ts deleted file mode 100644 index b3bfa97..0000000 --- a/src/internal/decoders/line.ts +++ /dev/null @@ -1,135 +0,0 @@ -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; -} diff --git a/src/internal/parse.ts b/src/internal/parse.ts index 914d001..a17b213 100644 --- a/src/internal/parse.ts +++ b/src/internal/parse.ts @@ -1,7 +1,6 @@ // 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 Parallel } from '../client'; import { formatRequestDetails, loggerFor } from './utils/log'; @@ -17,19 +16,6 @@ export type APIResponseProps = { export async function defaultParseResponse(client: Parallel, props: APIResponseProps): Promise { 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` - - if (props.options.__streamClass) { - return props.options.__streamClass.fromSSEResponse(response, props.controller, client) as any; - } - - return Stream.fromSSEResponse(response, props.controller, client) as any; - } - // fetch refuses to read the body when the status code is 204. if (response.status === 204) { return null as T; diff --git a/src/internal/request-options.ts b/src/internal/request-options.ts index 56765e5..2aabf9a 100644 --- a/src/internal/request-options.ts +++ b/src/internal/request-options.ts @@ -3,7 +3,6 @@ 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'; @@ -77,7 +76,6 @@ export type RequestOptions = { defaultBaseURL?: string | undefined; __binaryResponse?: boolean | undefined; - __streamClass?: typeof Stream; }; export type EncodedContent = { bodyHeaders: HeadersLike; body: BodyInit }; diff --git a/src/resources/beta/beta.ts b/src/resources/beta/beta.ts index c0de3d7..648f9c3 100644 --- a/src/resources/beta/beta.ts +++ b/src/resources/beta/beta.ts @@ -1,157 +1,21 @@ // 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 TaskGroupAPI from './task-group'; -import { - TaskGroup, - TaskGroupAddRunsParams, - TaskGroupCreateParams, - TaskGroupEventsParams, - TaskGroupEventsResponse, - TaskGroupGetRunsParams, - TaskGroupGetRunsResponse, - TaskGroupRunResponse, - TaskGroupStatus, -} from './task-group'; +import { TaskGroup } from './task-group'; import * as TaskRunAPI from './task-run'; -import { - BetaRunInput, - BetaTaskRunResult, - ErrorEvent, - McpServer, - McpToolCall, - ParallelBeta, - TaskRun, - TaskRunCreateParams, - TaskRunEvent, - TaskRunEventsResponse, - TaskRunResultParams, - Webhook, -} from './task-run'; -import { APIPromise } from '../../core/api-promise'; -import { RequestOptions } from '../../internal/request-options'; +import { TaskRun } from './task-run'; export class Beta extends APIResource { taskRun: TaskRunAPI.TaskRun = new TaskRunAPI.TaskRun(this._client); taskGroup: TaskGroupAPI.TaskGroup = new TaskGroupAPI.TaskGroup(this._client); - - /** - * Searches the web. - */ - search(body: BetaSearchParams, options?: RequestOptions): APIPromise { - return this._client.post('/v1beta/search', { body, ...options }); - } -} - -/** - * Output for the Search API. - */ -export interface SearchResult { - /** - * A list of WebSearchResult objects, ordered by decreasing relevance. - */ - results: Array; - - /** - * Search ID. Example: `search_cad0a6d2-dec0-46bd-95ae-900527d880e7` - */ - search_id: string; -} - -/** - * A single search result from the web search API. - */ -export interface WebSearchResult { - /** - * Text excerpts from the search result which are relevant to the request. - */ - excerpts: Array; - - /** - * Title of the search result. - */ - title: string; - - /** - * URL associated with the search result. - */ - url: string; -} - -export interface BetaSearchParams { - /** - * Upper bound on the number of characters to include in excerpts for each search - * result. - */ - max_chars_per_result?: number | null; - - /** - * Upper bound on the number of results to return. May be limited by the processor. - * Defaults to 10 if not provided. - */ - max_results?: number | null; - - /** - * Natural-language description of what the web search is trying to find. May - * include guidance about preferred sources or freshness. At least one of objective - * or search_queries must be provided. - */ - objective?: string | null; - - /** - * Search processor. - */ - processor?: 'base' | 'pro'; - - /** - * Optional list of traditional keyword search queries to guide the search. May - * contain search operators. At least one of objective or search_queries must be - * provided. - */ - search_queries?: Array | null; - - /** - * Source policy for web search results. - * - * This policy governs which sources are allowed/disallowed in results. - */ - source_policy?: Shared.SourcePolicy | null; } Beta.TaskRun = TaskRun; +Beta.TaskGroup = TaskGroup; export declare namespace Beta { - export { - type SearchResult as SearchResult, - type WebSearchResult as WebSearchResult, - type BetaSearchParams as BetaSearchParams, - }; - - export { - TaskRun as TaskRun, - type BetaRunInput as BetaRunInput, - type BetaTaskRunResult as BetaTaskRunResult, - type ErrorEvent as ErrorEvent, - type McpServer as McpServer, - type McpToolCall as McpToolCall, - type ParallelBeta as ParallelBeta, - type TaskRunEvent as TaskRunEvent, - type Webhook as Webhook, - type TaskRunEventsResponse as TaskRunEventsResponse, - type TaskRunCreateParams as TaskRunCreateParams, - type TaskRunResultParams as TaskRunResultParams, - }; + export { TaskRun as TaskRun }; - export { - type TaskGroup as TaskGroup, - type TaskGroupRunResponse as TaskGroupRunResponse, - type TaskGroupStatus as TaskGroupStatus, - type TaskGroupEventsResponse as TaskGroupEventsResponse, - type TaskGroupGetRunsResponse as TaskGroupGetRunsResponse, - type TaskGroupCreateParams as TaskGroupCreateParams, - type TaskGroupAddRunsParams as TaskGroupAddRunsParams, - type TaskGroupEventsParams as TaskGroupEventsParams, - type TaskGroupGetRunsParams as TaskGroupGetRunsParams, - }; + export { TaskGroup as TaskGroup }; } diff --git a/src/resources/beta/index.ts b/src/resources/beta/index.ts index 061e58c..bf1f5d2 100644 --- a/src/resources/beta/index.ts +++ b/src/resources/beta/index.ts @@ -1,28 +1,5 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. export { Beta } from './beta'; -export { - TaskGroup, - type TaskGroupRunResponse, - type TaskGroupStatus, - type TaskGroupEventsResponse, - type TaskGroupGetRunsResponse, - type TaskGroupCreateParams, - type TaskGroupAddRunsParams, - type TaskGroupEventsParams, - type TaskGroupGetRunsParams, -} from './task-group'; -export { - TaskRun, - type BetaRunInput, - type BetaTaskRunResult, - type ErrorEvent, - type McpServer, - type McpToolCall, - type ParallelBeta, - type TaskRunEvent, - type Webhook, - type TaskRunEventsResponse, - type TaskRunCreateParams, - type TaskRunResultParams, -} from './task-run'; +export { TaskGroup } from './task-group'; +export { TaskRun } from './task-run'; diff --git a/src/resources/beta/task-group.ts b/src/resources/beta/task-group.ts index f47f308..bfdaad5 100644 --- a/src/resources/beta/task-group.ts +++ b/src/resources/beta/task-group.ts @@ -1,270 +1,5 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../../core/resource'; -import * as TaskGroupAPI from './task-group'; -import * as TaskRunAPI from '../task-run'; -import * as BetaTaskRunAPI from './task-run'; -import { APIPromise } from '../../core/api-promise'; -import { Stream } from '../../core/streaming'; -import { buildHeaders } from '../../internal/headers'; -import { RequestOptions } from '../../internal/request-options'; -import { path } from '../../internal/utils/path'; -export class TaskGroup extends APIResource { - /** - * Initiates a TaskGroup to group and track multiple runs. - */ - create(body: TaskGroupCreateParams, options?: RequestOptions): APIPromise { - return this._client.post('/v1beta/tasks/groups', { body, ...options }); - } - - /** - * Retrieves aggregated status across runs in a TaskGroup. - */ - retrieve(taskGroupID: string, options?: RequestOptions): APIPromise { - return this._client.get(path`/v1beta/tasks/groups/${taskGroupID}`, options); - } - - /** - * Initiates multiple task runs within a TaskGroup. - */ - addRuns( - taskGroupID: string, - params: TaskGroupAddRunsParams, - options?: RequestOptions, - ): APIPromise { - const { betas, ...body } = params; - return this._client.post(path`/v1beta/tasks/groups/${taskGroupID}/runs`, { - body, - ...options, - headers: buildHeaders([ - { ...(betas?.toString() != null ? { 'parallel-beta': betas?.toString() } : undefined) }, - options?.headers, - ]), - }); - } - - /** - * Streams events from a TaskGroup: status updates and run completions. - * - * The connection will remain open for up to 10 minutes as long as at least one run - * in the TaskGroup is active. - */ - events( - taskGroupID: string, - query: TaskGroupEventsParams | undefined = {}, - options?: RequestOptions, - ): APIPromise> { - return this._client.get(path`/v1beta/tasks/groups/${taskGroupID}/events`, { - query, - ...options, - headers: buildHeaders([{ Accept: 'text/event-stream' }, options?.headers]), - stream: true, - }) as APIPromise>; - } - - /** - * Retrieves task runs in a TaskGroup and optionally their inputs and outputs. - */ - getRuns( - taskGroupID: string, - query: TaskGroupGetRunsParams | undefined = {}, - options?: RequestOptions, - ): APIPromise> { - return this._client.get(path`/v1beta/tasks/groups/${taskGroupID}/runs`, { - query, - ...options, - headers: buildHeaders([{ Accept: 'text/event-stream' }, options?.headers]), - stream: true, - }) as APIPromise>; - } -} - -/** - * Response object for a task group, including its status and metadata. - */ -export interface TaskGroup { - /** - * Timestamp of the creation of the group, as an RFC 3339 string. - */ - created_at: string | null; - - /** - * Status of a task group. - */ - status: TaskGroupStatus; - - /** - * ID of the group. - */ - taskgroup_id: string; - - /** - * User-provided metadata stored with the group. - */ - metadata?: { [key: string]: string | number | boolean } | null; -} - -/** - * Response from adding new task runs to a task group. - */ -export interface TaskGroupRunResponse { - /** - * Cursor for these runs in the event stream at - * taskgroup/events?last_event_id=. Empty for the first runs in the - * group. - */ - event_cursor: string | null; - - /** - * Cursor for these runs in the run stream at - * taskgroup/runs?last_event_id=. Empty for the first runs in the - * group. - */ - run_cursor: string | null; - - /** - * IDs of the newly created runs. - */ - run_ids: Array; - - /** - * Status of a task group. - */ - status: TaskGroupStatus; -} - -/** - * Status of a task group. - */ -export interface TaskGroupStatus { - /** - * True if at least one run in the group is currently active, i.e. status is one of - * {'cancelling', 'queued', 'running'}. - */ - is_active: boolean; - - /** - * Timestamp of the last status update to the group, as an RFC 3339 string. - */ - modified_at: string | null; - - /** - * Number of task runs in the group. - */ - num_task_runs: number; - - /** - * Human-readable status message for the group. - */ - status_message: string | null; - - /** - * Number of task runs with each status. - */ - task_run_status_counts: { [key: string]: number }; -} - -/** - * Event indicating an update to group status. - */ -export type TaskGroupEventsResponse = - | TaskGroupEventsResponse.TaskGroupStatusEvent - | BetaTaskRunAPI.TaskRunEvent - | BetaTaskRunAPI.ErrorEvent; - -export namespace TaskGroupEventsResponse { - /** - * Event indicating an update to group status. - */ - export interface TaskGroupStatusEvent { - /** - * Cursor to resume the event stream. - */ - event_id: string; - - /** - * Status of a task group. - */ - status: TaskGroupAPI.TaskGroupStatus; - - /** - * Event type; always 'task_group_status'. - */ - type: 'task_group_status'; - } -} - -/** - * Event when a task run transitions to a non-active status. - * - * May indicate completion, cancellation, or failure. - */ -export type TaskGroupGetRunsResponse = BetaTaskRunAPI.TaskRunEvent | BetaTaskRunAPI.ErrorEvent; - -export interface TaskGroupCreateParams { - /** - * User-provided metadata stored with the task group. - */ - metadata?: { [key: string]: string | number | boolean } | null; -} - -export interface TaskGroupAddRunsParams { - /** - * Body param: List of task runs to execute. - */ - inputs: Array; - - /** - * Body param: Specification for a task. - * - * Auto output schemas can be specified by setting `output_schema={"type":"auto"}`. - * Not specifying a TaskSpec is the same as setting an auto output schema. - * - * For convenience bare strings are also accepted as input or output schemas. - */ - default_task_spec?: TaskRunAPI.TaskSpec | null; - - /** - * Header param: Optional header to specify the beta version(s) to enable. - */ - betas?: Array; -} - -export interface TaskGroupEventsParams { - last_event_id?: string | null; - - timeout?: number | null; -} - -export interface TaskGroupGetRunsParams { - include_input?: boolean; - - include_output?: boolean; - - last_event_id?: string | null; - - status?: - | 'queued' - | 'action_required' - | 'running' - | 'completed' - | 'failed' - | 'cancelling' - | 'cancelled' - | null; -} - -export declare namespace TaskGroup { - export { - type TaskGroup as TaskGroup, - type TaskGroupRunResponse as TaskGroupRunResponse, - type TaskGroupStatus as TaskGroupStatus, - type TaskGroupEventsResponse as TaskGroupEventsResponse, - type TaskGroupGetRunsResponse as TaskGroupGetRunsResponse, - type TaskGroupCreateParams as TaskGroupCreateParams, - type TaskGroupAddRunsParams as TaskGroupAddRunsParams, - type TaskGroupEventsParams as TaskGroupEventsParams, - type TaskGroupGetRunsParams as TaskGroupGetRunsParams, - }; -} +export class TaskGroup extends APIResource {} diff --git a/src/resources/beta/task-run.ts b/src/resources/beta/task-run.ts index bfaf20f..e42a175 100644 --- a/src/resources/beta/task-run.ts +++ b/src/resources/beta/task-run.ts @@ -1,517 +1,5 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../../core/resource'; -import * as BetaTaskRunAPI from './task-run'; -import * as Shared from '../shared'; -import * as TaskRunAPI from '../task-run'; -import { APIPromise } from '../../core/api-promise'; -import { Stream } from '../../core/streaming'; -import { buildHeaders } from '../../internal/headers'; -import { RequestOptions } from '../../internal/request-options'; -import { path } from '../../internal/utils/path'; -export class TaskRun extends APIResource { - /** - * Initiates a task run. - * - * Returns immediately with a run object in status 'queued'. - * - * Beta features can be enabled by setting the 'parallel-beta' header. - */ - create(params: TaskRunCreateParams, options?: RequestOptions): APIPromise { - const { betas, ...body } = params; - return this._client.post('/v1/tasks/runs?beta=true', { - body, - ...options, - headers: buildHeaders([ - { ...(betas?.toString() != null ? { 'parallel-beta': betas?.toString() } : undefined) }, - options?.headers, - ]), - }); - } - - /** - * Streams events for a task run. - * - * Returns a stream of events showing progress updates and state changes for the - * task run. - * - * For task runs that did not have enable_events set to true during creation, the - * frequency of events will be reduced. - */ - events(runID: string, options?: RequestOptions): APIPromise> { - return this._client.get(path`/v1beta/tasks/runs/${runID}/events`, { - ...options, - headers: buildHeaders([{ Accept: 'text/event-stream' }, options?.headers]), - stream: true, - }) as APIPromise>; - } - - /** - * Retrieves a run result by run_id, blocking until the run is completed. - */ - result( - runID: string, - params: TaskRunResultParams | null | undefined = {}, - options?: RequestOptions, - ): APIPromise { - const { betas, ...query } = params ?? {}; - return this._client.get(path`/v1/tasks/runs/${runID}/result?beta=true`, { - query, - ...options, - headers: buildHeaders([ - { ...(betas?.toString() != null ? { 'parallel-beta': betas?.toString() } : undefined) }, - options?.headers, - ]), - }); - } -} - -/** - * Task run input with additional beta fields. - */ -export interface BetaRunInput { - /** - * Input to the task, either text or a JSON object. - */ - input: string | { [key: string]: unknown }; - - /** - * Processor to use for the task. - */ - processor: string; - - /** - * Controls tracking of task run execution progress. When set to true, progress - * events are recorded and can be accessed via the - * [Task Run events](https://platform.parallel.ai/api-reference) endpoint. When - * false, no progress events are tracked. Note that progress tracking cannot be - * enabled after a run has been created. The flag is set to true by default for - * premium processors (pro and above). This feature is not available via the Python - * SDK. To enable this feature in your API requests, specify the `parallel-beta` - * header with `events-sse-2025-07-24` value. - */ - enable_events?: boolean | null; - - /** - * Optional list of MCP servers to use for the run. This feature is not available - * via the Python SDK. To enable this feature in your API requests, specify the - * `parallel-beta` header with `mcp-server-2025-07-17` value. - */ - mcp_servers?: Array | null; - - /** - * User-provided metadata stored with the run. Keys and values must be strings with - * a maximum length of 16 and 512 characters respectively. - */ - metadata?: { [key: string]: string | number | boolean } | null; - - /** - * Source policy for web search results. - * - * This policy governs which sources are allowed/disallowed in results. - */ - source_policy?: Shared.SourcePolicy | null; - - /** - * Specification for a task. - * - * Auto output schemas can be specified by setting `output_schema={"type":"auto"}`. - * Not specifying a TaskSpec is the same as setting an auto output schema. - * - * For convenience bare strings are also accepted as input or output schemas. - */ - task_spec?: TaskRunAPI.TaskSpec | null; - - /** - * Webhooks for Task Runs. - */ - webhook?: Webhook | null; -} - -/** - * Result of a beta task run. Available only if beta headers are specified. - */ -export interface BetaTaskRunResult { - /** - * Output from the task conforming to the output schema. - */ - output: BetaTaskRunResult.BetaTaskRunTextOutput | BetaTaskRunResult.BetaTaskRunJsonOutput; - - /** - * Status of a task run. - */ - run: TaskRunAPI.TaskRun; -} - -export namespace BetaTaskRunResult { - /** - * Output from a task that returns text. - */ - export interface BetaTaskRunTextOutput { - /** - * Basis for the output. - */ - basis: Array; - - /** - * Text output from the task. - */ - content: string; - - /** - * The type of output being returned, as determined by the output schema of the - * task spec. - */ - type: 'text'; - - /** - * Always None. - */ - beta_fields?: { [key: string]: unknown } | null; - - /** - * MCP tool calls made by the task. - */ - mcp_tool_calls?: Array | null; - } - - /** - * Output from a task that returns JSON. - */ - export interface BetaTaskRunJsonOutput { - /** - * Basis for the output. - */ - basis: Array; - - /** - * Output from the task as a native JSON object, as determined by the output schema - * of the task spec. - */ - content: { [key: string]: unknown }; - - /** - * The type of output being returned, as determined by the output schema of the - * task spec. - */ - type: 'json'; - - /** - * Always None. - */ - beta_fields?: { [key: string]: unknown } | null; - - /** - * MCP tool calls made by the task. - */ - mcp_tool_calls?: Array | null; - - /** - * Output schema for the Task Run. Populated only if the task was executed with an - * auto schema. - */ - output_schema?: { [key: string]: unknown } | null; - } -} - -/** - * Event indicating an error. - */ -export interface ErrorEvent { - /** - * An error message. - */ - error: Shared.ErrorObject; - - /** - * Event type; always 'error'. - */ - type: 'error'; -} - -/** - * MCP server configuration. - */ -export interface McpServer { - /** - * Name of the MCP server. - */ - name: string; - - /** - * URL of the MCP server. - */ - url: string; - - /** - * List of allowed tools for the MCP server. - */ - allowed_tools?: Array | null; - - /** - * Headers for the MCP server. - */ - headers?: { [key: string]: string } | null; - - /** - * Type of MCP server being configured. Always `url`. - */ - type?: 'url'; -} - -/** - * Result of an MCP tool call. - */ -export interface McpToolCall { - /** - * Arguments used to call the MCP tool. - */ - arguments: string; - - /** - * Name of the MCP server. - */ - server_name: string; - - /** - * Identifier for the tool call. - */ - tool_call_id: string; - - /** - * Name of the tool being called. - */ - tool_name: string; - - /** - * Output received from the tool call, if successful. - */ - content?: string | null; - - /** - * Error message if the tool call failed. - */ - error?: string | null; -} - -/** - * Model for the parallel-beta header. - */ -export type ParallelBeta = - | 'mcp-server-2025-07-17' - | 'events-sse-2025-07-24' - | 'webhook-2025-08-12' - | (string & {}); - -/** - * Event when a task run transitions to a non-active status. - * - * May indicate completion, cancellation, or failure. - */ -export interface TaskRunEvent { - /** - * Cursor to resume the event stream. Always empty for non Task Group runs. - */ - event_id: string | null; - - /** - * Status of a task run. - */ - run: TaskRunAPI.TaskRun; - - /** - * Event type; always 'task_run.state'. - */ - type: 'task_run.state'; - - /** - * Task run input with additional beta fields. - */ - input?: BetaRunInput | null; - - /** - * Output from the run; included only if requested and if status == `completed`. - */ - output?: TaskRunAPI.TaskRunTextOutput | TaskRunAPI.TaskRunJsonOutput | null; -} - -/** - * Webhooks for Task Runs. - */ -export interface Webhook { - /** - * URL for the webhook. - */ - url: string; - - /** - * Event types to send the webhook notifications for. - */ - event_types?: Array<'task_run.status'>; -} - -/** - * A progress update for a task run. - */ -export type TaskRunEventsResponse = - | TaskRunEventsResponse.TaskRunProgressStatsEvent - | TaskRunEventsResponse.TaskRunProgressMessageEvent - | TaskRunEvent - | ErrorEvent; - -export namespace TaskRunEventsResponse { - /** - * A progress update for a task run. - */ - export interface TaskRunProgressStatsEvent { - /** - * Source stats for a task run. - */ - source_stats: TaskRunProgressStatsEvent.SourceStats; - - /** - * Event type; always 'task_run.progress_stats'. - */ - type: 'task_run.progress_stats'; - } - - export namespace TaskRunProgressStatsEvent { - /** - * Source stats for a task run. - */ - export interface SourceStats { - /** - * Number of sources considered in processing the task. - */ - num_sources_considered: number | null; - - /** - * Number of sources read in processing the task. - */ - num_sources_read: number | null; - - /** - * A sample of URLs of sources read in processing the task. - */ - sources_read_sample: Array | null; - } - } - - /** - * A message for a task run progress update. - */ - export interface TaskRunProgressMessageEvent { - /** - * Progress update message. - */ - message: string; - - /** - * Timestamp of the message. - */ - timestamp: string | null; - - /** - * Event type; always starts with 'task_run.progress_msg'. - */ - type: - | 'task_run.progress_msg.plan' - | 'task_run.progress_msg.search' - | 'task_run.progress_msg.result' - | 'task_run.progress_msg.tool_call' - | 'task_run.progress_msg.exec_status'; - } -} - -export interface TaskRunCreateParams { - /** - * Body param: Input to the task, either text or a JSON object. - */ - input: string | { [key: string]: unknown }; - - /** - * Body param: Processor to use for the task. - */ - processor: string; - - /** - * Body param: Controls tracking of task run execution progress. When set to true, - * progress events are recorded and can be accessed via the - * [Task Run events](https://platform.parallel.ai/api-reference) endpoint. When - * false, no progress events are tracked. Note that progress tracking cannot be - * enabled after a run has been created. The flag is set to true by default for - * premium processors (pro and above). This feature is not available via the Python - * SDK. To enable this feature in your API requests, specify the `parallel-beta` - * header with `events-sse-2025-07-24` value. - */ - enable_events?: boolean | null; - - /** - * Body param: Optional list of MCP servers to use for the run. This feature is not - * available via the Python SDK. To enable this feature in your API requests, - * specify the `parallel-beta` header with `mcp-server-2025-07-17` value. - */ - mcp_servers?: Array | null; - - /** - * Body param: User-provided metadata stored with the run. Keys and values must be - * strings with a maximum length of 16 and 512 characters respectively. - */ - metadata?: { [key: string]: string | number | boolean } | null; - - /** - * Body param: Source policy for web search results. - * - * This policy governs which sources are allowed/disallowed in results. - */ - source_policy?: Shared.SourcePolicy | null; - - /** - * Body param: Specification for a task. - * - * Auto output schemas can be specified by setting `output_schema={"type":"auto"}`. - * Not specifying a TaskSpec is the same as setting an auto output schema. - * - * For convenience bare strings are also accepted as input or output schemas. - */ - task_spec?: TaskRunAPI.TaskSpec | null; - - /** - * Body param: Webhooks for Task Runs. - */ - webhook?: Webhook | null; - - /** - * Header param: Optional header to specify the beta version(s) to enable. - */ - betas?: Array; -} - -export interface TaskRunResultParams { - /** - * Query param: - */ - timeout?: number; - - /** - * Header param: Optional header to specify the beta version(s) to enable. - */ - betas?: Array; -} - -export declare namespace TaskRun { - export { - type BetaRunInput as BetaRunInput, - type BetaTaskRunResult as BetaTaskRunResult, - type ErrorEvent as ErrorEvent, - type McpServer as McpServer, - type McpToolCall as McpToolCall, - type ParallelBeta as ParallelBeta, - type TaskRunEvent as TaskRunEvent, - type Webhook as Webhook, - type TaskRunEventsResponse as TaskRunEventsResponse, - type TaskRunCreateParams as TaskRunCreateParams, - type TaskRunResultParams as TaskRunResultParams, - }; -} +export class TaskRun extends APIResource {} diff --git a/src/resources/index.ts b/src/resources/index.ts index 709471e..911d9ca 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -4,11 +4,8 @@ export * from './shared'; export { Beta } from './beta/beta'; export { TaskRun, - type AutoSchema, type Citation, - type FieldBasis, type JsonSchema, - type RunInput, type TaskRunJsonOutput, type TaskRunResult, type TaskRunTextOutput, diff --git a/src/resources/shared.ts b/src/resources/shared.ts index 5bb1c1a..0ec2535 100644 --- a/src/resources/shared.ts +++ b/src/resources/shared.ts @@ -17,7 +17,7 @@ export interface ErrorObject { /** * Optional detail supporting the error. */ - detail?: { [key: string]: unknown } | null; + detail?: unknown | null; } /** @@ -32,45 +32,5 @@ export interface ErrorResponse { /** * Always 'error'. */ - type: 'error'; -} - -/** - * Source policy for web search results. - * - * This policy governs which sources are allowed/disallowed in results. - */ -export interface SourcePolicy { - /** - * List of domains to exclude from results. If specified, sources from these - * domains will be excluded. - */ - exclude_domains?: Array; - - /** - * List of domains to restrict the results to. If specified, only sources from - * these domains will be included. - */ - include_domains?: Array; -} - -/** - * Human-readable message for a task. - */ -export interface Warning { - /** - * Human-readable message. - */ - message: string; - - /** - * Type of warning. Note that adding new warning types is considered a - * backward-compatible change. - */ - type: 'spec_validation_warning' | 'input_validation_warning' | 'warning'; - - /** - * Optional detail supporting the warning. - */ - detail?: { [key: string]: unknown } | null; + type?: 'error'; } diff --git a/src/resources/task-run.ts b/src/resources/task-run.ts index 6cada92..bac8e38 100644 --- a/src/resources/task-run.ts +++ b/src/resources/task-run.ts @@ -1,34 +1,28 @@ // 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 TaskRunAPI from './task-run'; import { APIPromise } from '../core/api-promise'; import { RequestOptions } from '../internal/request-options'; import { path } from '../internal/utils/path'; export class TaskRun extends APIResource { /** - * Initiates a task run. - * - * Returns immediately with a run object in status 'queued'. - * - * Beta features can be enabled by setting the 'parallel-beta' header. + * Initiates a single task run. */ create(body: TaskRunCreateParams, options?: RequestOptions): APIPromise { return this._client.post('/v1/tasks/runs', { body, ...options }); } /** - * Retrieves run status by run_id. - * - * The run result is available from the `/result` endpoint. + * Retrieves a run by run_id. */ retrieve(runID: string, options?: RequestOptions): APIPromise { return this._client.get(path`/v1/tasks/runs/${runID}`, options); } /** - * Retrieves a run result by run_id, blocking until the run is completed. + * Retrieves a run by run_id, blocking until the run is completed. */ result( runID: string, @@ -39,16 +33,6 @@ export class TaskRun extends APIResource { } } -/** - * Auto schema for a task input or output. - */ -export interface AutoSchema { - /** - * The type of schema being defined. Always `auto`. - */ - type?: 'auto'; -} - /** * A citation for a task output. */ @@ -70,32 +54,6 @@ export interface Citation { title?: string | null; } -/** - * Citations and reasoning supporting one field of a task output. - */ -export interface FieldBasis { - /** - * Name of the output field. - */ - field: string; - - /** - * Reasoning for the output field. - */ - reasoning: string; - - /** - * List of citations supporting the output field. - */ - citations?: Array; - - /** - * Confidence level for the output field. Only certain processors provide - * confidence levels. - */ - confidence?: string | null; -} - /** * JSON schema for a task input or output. */ @@ -103,7 +61,7 @@ export interface JsonSchema { /** * A JSON Schema object. Only a subset of JSON Schema is supported. */ - json_schema: { [key: string]: unknown }; + json_schema: unknown; /** * The type of schema being defined. Always `json`. @@ -112,45 +70,7 @@ export interface JsonSchema { } /** - * Request to run a task. - */ -export interface RunInput { - /** - * Input to the task, either text or a JSON object. - */ - input: string | { [key: string]: unknown }; - - /** - * Processor to use for the task. - */ - processor: string; - - /** - * User-provided metadata stored with the run. Keys and values must be strings with - * a maximum length of 16 and 512 characters respectively. - */ - metadata?: { [key: string]: string | number | boolean } | null; - - /** - * Source policy for web search results. - * - * This policy governs which sources are allowed/disallowed in results. - */ - source_policy?: Shared.SourcePolicy | null; - - /** - * Specification for a task. - * - * Auto output schemas can be specified by setting `output_schema={"type":"auto"}`. - * Not specifying a TaskSpec is the same as setting an auto output schema. - * - * For convenience bare strings are also accepted as input or output schemas. - */ - task_spec?: TaskSpec | null; -} - -/** - * Status of a task run. + * Status of a task. */ export interface TaskRun { /** @@ -159,8 +79,8 @@ export interface TaskRun { created_at: string | null; /** - * Whether the run is currently active, i.e. status is one of {'cancelling', - * 'queued', 'running'}. + * Whether the run is currently active; i.e. status is one of {'running', 'queued', + * 'cancelling'}. */ is_active: boolean; @@ -184,67 +104,88 @@ export interface TaskRun { */ status: 'queued' | 'action_required' | 'running' | 'completed' | 'failed' | 'cancelling' | 'cancelled'; - /** - * An error message. - */ - error?: Shared.ErrorObject | null; - /** * User-provided metadata stored with the run. */ metadata?: { [key: string]: string | number | boolean } | null; /** - * ID of the taskgroup to which the run belongs. + * Warnings for the run. */ - taskgroup_id?: string | null; + warnings?: Array | null; +} +export namespace TaskRun { /** - * Warnings for the run, if any. + * Human-readable message for a task. */ - warnings?: Array | null; + export interface Warning { + /** + * Human-readable message. + */ + message: string; + + /** + * Type of warning. Note that adding new warning types is considered a + * backward-compatible change. + */ + type: string; + + /** + * Optional detail supporting the warning. + */ + detail?: unknown | null; + } } /** - * Output from a task that returns JSON. + * Output from a task that returns text. */ export interface TaskRunJsonOutput { /** * Basis for each top-level field in the JSON output. */ - basis: Array; + basis: Array; /** * Output from the task as a native JSON object, as determined by the output schema * of the task spec. */ - content: { [key: string]: unknown }; + content: unknown; /** * The type of output being returned, as determined by the output schema of the * task spec. */ type: 'json'; +} +export namespace TaskRunJsonOutput { /** - * Additional fields from beta features used in this task run. When beta features - * are specified during both task run creation and result retrieval, this field - * will be empty and instead the relevant beta attributes will be directly included - * in the `BetaTaskRunJsonOutput` or corresponding output type. However, if beta - * features were specified during task run creation but not during result - * retrieval, this field will contain the dump of fields from those beta features. - * Each key represents the beta feature version (one amongst parallel-beta headers) - * and the values correspond to the beta feature attributes, if any. For now, only - * MCP server beta features have attributes. For example, - * `{mcp-server-2025-07-17: [{'server_name':'mcp_server', 'tool_call_id': 'tc_123', ...}]}}` + * Citations and reasoning supporting one field of a task output. */ - beta_fields?: { [key: string]: unknown } | null; + export interface Basis { + /** + * Name of the output field. + */ + field: string; - /** - * Output schema for the Task Run. Populated only if the task was executed with an - * auto schema. - */ - output_schema?: { [key: string]: unknown } | null; + /** + * Reasoning for the output field. + */ + reasoning: string; + + /** + * List of citations supporting the output field. + */ + citations?: Array; + + /** + * Confidence level for the output field. Only certain processors provide + * confidence levels. + */ + confidence?: string | null; + } } /** @@ -257,7 +198,7 @@ export interface TaskRunResult { output: TaskRunTextOutput | TaskRunJsonOutput; /** - * Status of a task run. + * Status of a task. */ run: TaskRun; } @@ -269,7 +210,7 @@ export interface TaskRunTextOutput { /** * Basis for the output. The basis has a single field 'output'. */ - basis: Array; + basis: Array; /** * Text output from the task. @@ -281,29 +222,41 @@ export interface TaskRunTextOutput { * task spec. */ type: 'text'; +} +export namespace TaskRunTextOutput { /** - * Additional fields from beta features used in this task run. When beta features - * are specified during both task run creation and result retrieval, this field - * will be empty and instead the relevant beta attributes will be directly included - * in the `BetaTaskRunJsonOutput` or corresponding output type. However, if beta - * features were specified during task run creation but not during result - * retrieval, this field will contain the dump of fields from those beta features. - * Each key represents the beta feature version (one amongst parallel-beta headers) - * and the values correspond to the beta feature attributes, if any. For now, only - * MCP server beta features have attributes. For example, - * `{mcp-server-2025-07-17: [{'server_name':'mcp_server', 'tool_call_id': 'tc_123', ...}]}}` + * Citations and reasoning supporting one field of a task output. */ - beta_fields?: { [key: string]: unknown } | null; + export interface Basis { + /** + * Name of the output field. + */ + field: string; + + /** + * Reasoning for the output field. + */ + reasoning: string; + + /** + * List of citations supporting the output field. + */ + citations?: Array; + + /** + * Confidence level for the output field. Only certain processors provide + * confidence levels. + */ + confidence?: string | null; + } } /** * Specification for a task. * - * Auto output schemas can be specified by setting `output_schema={"type":"auto"}`. - * Not specifying a TaskSpec is the same as setting an auto output schema. - * - * For convenience bare strings are also accepted as input or output schemas. + * For convenience we allow bare strings as input or output schemas, which is + * equivalent to a text schema with the same description. */ export interface TaskSpec { /** @@ -312,13 +265,13 @@ export interface TaskSpec { * response. A bare string is equivalent to a text schema with the same * description. */ - output_schema: JsonSchema | TextSchema | AutoSchema | string; + output_schema: JsonSchema | TextSchema | string; /** * Optional JSON schema or text description of expected input to the task. A bare * string is equivalent to a text schema with the same description. */ - input_schema?: string | JsonSchema | TextSchema | null; + input_schema?: JsonSchema | TextSchema | string | null; } /** @@ -340,7 +293,7 @@ export interface TaskRunCreateParams { /** * Input to the task, either text or a JSON object. */ - input: string | { [key: string]: unknown }; + input: string | unknown; /** * Processor to use for the task. @@ -353,20 +306,11 @@ export interface TaskRunCreateParams { */ metadata?: { [key: string]: string | number | boolean } | null; - /** - * Source policy for web search results. - * - * This policy governs which sources are allowed/disallowed in results. - */ - source_policy?: Shared.SourcePolicy | null; - /** * Specification for a task. * - * Auto output schemas can be specified by setting `output_schema={"type":"auto"}`. - * Not specifying a TaskSpec is the same as setting an auto output schema. - * - * For convenience bare strings are also accepted as input or output schemas. + * For convenience we allow bare strings as input or output schemas, which is + * equivalent to a text schema with the same description. */ task_spec?: TaskSpec | null; } @@ -377,11 +321,8 @@ export interface TaskRunResultParams { export declare namespace TaskRun { export { - type AutoSchema as AutoSchema, type Citation as Citation, - type FieldBasis as FieldBasis, type JsonSchema as JsonSchema, - type RunInput as RunInput, type TaskRun as TaskRun, type TaskRunJsonOutput as TaskRunJsonOutput, type TaskRunResult as TaskRunResult, diff --git a/src/streaming.ts b/src/streaming.ts deleted file mode 100644 index 9e6da10..0000000 --- a/src/streaming.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Import from ./core/streaming instead */ -export * from './core/streaming'; diff --git a/tests/api-resources/beta/beta.test.ts b/tests/api-resources/beta/beta.test.ts deleted file mode 100644 index f6a03e3..0000000 --- a/tests/api-resources/beta/beta.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import Parallel from 'parallel-web'; - -const client = new Parallel({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource beta', () => { - test('search', async () => { - const responsePromise = client.beta.search({}); - 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); - }); -}); diff --git a/tests/api-resources/beta/task-group.test.ts b/tests/api-resources/beta/task-group.test.ts deleted file mode 100644 index 612b8bc..0000000 --- a/tests/api-resources/beta/task-group.test.ts +++ /dev/null @@ -1,126 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import Parallel from 'parallel-web'; - -const client = new Parallel({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource taskGroup', () => { - test('create', async () => { - const responsePromise = client.beta.taskGroup.create({}); - 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); - }); - - test('retrieve', async () => { - const responsePromise = client.beta.taskGroup.retrieve('taskgroup_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); - }); - - test('addRuns: only required params', async () => { - const responsePromise = client.beta.taskGroup.addRuns('taskgroup_id', { - inputs: [{ input: 'What was the GDP of France in 2023?', processor: 'base' }], - }); - 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); - }); - - test('addRuns: required and optional params', async () => { - const response = await client.beta.taskGroup.addRuns('taskgroup_id', { - inputs: [ - { - input: 'What was the GDP of France in 2023?', - processor: 'base', - enable_events: true, - mcp_servers: [ - { name: 'name', url: 'url', allowed_tools: ['string'], headers: { foo: 'string' }, type: 'url' }, - ], - metadata: { foo: 'string' }, - source_policy: { exclude_domains: ['string'], include_domains: ['string'] }, - task_spec: { - output_schema: { - json_schema: { additionalProperties: 'bar', properties: 'bar', required: 'bar', type: 'bar' }, - type: 'json', - }, - input_schema: 'string', - }, - webhook: { url: 'url', event_types: ['task_run.status'] }, - }, - ], - default_task_spec: { - output_schema: { - json_schema: { additionalProperties: 'bar', properties: 'bar', required: 'bar', type: 'bar' }, - type: 'json', - }, - input_schema: 'string', - }, - betas: ['mcp-server-2025-07-17'], - }); - }); - - // Prism doesn't support text/event-stream responses - test.skip('events', async () => { - const responsePromise = client.beta.taskGroup.events('taskgroup_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 text/event-stream responses - test.skip('events: 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.taskGroup.events( - 'taskgroup_id', - { last_event_id: 'last_event_id', timeout: 0 }, - { path: '/_stainless_unknown_path' }, - ), - ).rejects.toThrow(Parallel.NotFoundError); - }); - - // Prism doesn't support text/event-stream responses - test.skip('getRuns', async () => { - const responsePromise = client.beta.taskGroup.getRuns('taskgroup_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 text/event-stream responses - test.skip('getRuns: 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.taskGroup.getRuns( - 'taskgroup_id', - { include_input: true, include_output: true, last_event_id: 'last_event_id', status: 'queued' }, - { path: '/_stainless_unknown_path' }, - ), - ).rejects.toThrow(Parallel.NotFoundError); - }); -}); diff --git a/tests/api-resources/beta/task-run.test.ts b/tests/api-resources/beta/task-run.test.ts deleted file mode 100644 index 29adaa3..0000000 --- a/tests/api-resources/beta/task-run.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import Parallel from 'parallel-web'; - -const client = new Parallel({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource taskRun', () => { - test('create: only required params', async () => { - const responsePromise = client.beta.taskRun.create({ - input: 'What was the GDP of France in 2023?', - processor: 'base', - }); - 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); - }); - - test('create: required and optional params', async () => { - const response = await client.beta.taskRun.create({ - input: 'What was the GDP of France in 2023?', - processor: 'base', - enable_events: true, - mcp_servers: [ - { name: 'name', url: 'url', allowed_tools: ['string'], headers: { foo: 'string' }, type: 'url' }, - ], - metadata: { foo: 'string' }, - source_policy: { exclude_domains: ['string'], include_domains: ['string'] }, - task_spec: { - output_schema: { - json_schema: { additionalProperties: 'bar', properties: 'bar', required: 'bar', type: 'bar' }, - type: 'json', - }, - input_schema: 'string', - }, - webhook: { url: 'url', event_types: ['task_run.status'] }, - betas: ['mcp-server-2025-07-17'], - }); - }); - - // Prism doesn't support text/event-stream responses - test.skip('events', async () => { - const responsePromise = client.beta.taskRun.events('run_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); - }); - - test('result', async () => { - const responsePromise = client.beta.taskRun.result('run_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); - }); - - test('result: 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.taskRun.result( - 'run_id', - { timeout: 0, betas: ['mcp-server-2025-07-17'] }, - { path: '/_stainless_unknown_path' }, - ), - ).rejects.toThrow(Parallel.NotFoundError); - }); -}); diff --git a/tests/api-resources/task-run.test.ts b/tests/api-resources/task-run.test.ts index eedd2bb..be6e12b 100644 --- a/tests/api-resources/task-run.test.ts +++ b/tests/api-resources/task-run.test.ts @@ -9,10 +9,7 @@ const client = new Parallel({ describe('resource taskRun', () => { test('create: only required params', async () => { - const responsePromise = client.taskRun.create({ - input: 'What was the GDP of France in 2023?', - processor: 'base', - }); + const responsePromise = client.taskRun.create({ input: 'France (2023)', processor: 'processor' }); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -24,16 +21,38 @@ describe('resource taskRun', () => { test('create: required and optional params', async () => { const response = await client.taskRun.create({ - input: 'What was the GDP of France in 2023?', - processor: 'base', + input: 'France (2023)', + processor: 'processor', metadata: { foo: 'string' }, - source_policy: { exclude_domains: ['string'], include_domains: ['string'] }, task_spec: { output_schema: { - json_schema: { additionalProperties: 'bar', properties: 'bar', required: 'bar', type: 'bar' }, + json_schema: { + additionalProperties: false, + properties: { + gdp: { + description: "GDP in USD for the year, formatted like '$3.1 trillion (2023)'", + type: 'string', + }, + }, + required: ['gdp'], + type: 'object', + }, + type: 'json', + }, + input_schema: { + json_schema: { + additionalProperties: false, + properties: { + gdp: { + description: "GDP in USD for the year, formatted like '$3.1 trillion (2023)'", + type: 'string', + }, + }, + required: ['gdp'], + type: 'object', + }, type: 'json', }, - input_schema: 'string', }, }); }); diff --git a/tests/internal/decoders/line.test.ts b/tests/internal/decoders/line.test.ts deleted file mode 100644 index 13e60b9..0000000 --- a/tests/internal/decoders/line.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { findDoubleNewlineIndex, LineDecoder } from 'parallel-web/internal/decoders/line'; - -function decodeChunks(chunks: string[], { flush }: { flush: boolean } = { flush: false }): string[] { - const decoder = new LineDecoder(); - const lines: string[] = []; - for (const chunk of chunks) { - lines.push(...decoder.decode(chunk)); - } - - if (flush) { - lines.push(...decoder.flush()); - } - - return lines; -} - -describe('line decoder', () => { - test('basic', () => { - // baz is not included because the line hasn't ended yet - expect(decodeChunks(['foo', ' bar\nbaz'])).toEqual(['foo bar']); - }); - - test('basic with \\r', () => { - expect(decodeChunks(['foo', ' bar\r\nbaz'])).toEqual(['foo bar']); - expect(decodeChunks(['foo', ' bar\r\nbaz'], { flush: true })).toEqual(['foo bar', 'baz']); - }); - - test('trailing new lines', () => { - expect(decodeChunks(['foo', ' bar', 'baz\n', 'thing\n'])).toEqual(['foo barbaz', 'thing']); - }); - - test('trailing new lines with \\r', () => { - expect(decodeChunks(['foo', ' bar', 'baz\r\n', 'thing\r\n'])).toEqual(['foo barbaz', 'thing']); - }); - - test('escaped new lines', () => { - expect(decodeChunks(['foo', ' bar\\nbaz\n'])).toEqual(['foo bar\\nbaz']); - }); - - test('escaped new lines with \\r', () => { - expect(decodeChunks(['foo', ' bar\\r\\nbaz\n'])).toEqual(['foo bar\\r\\nbaz']); - }); - - test('\\r & \\n split across multiple chunks', () => { - expect(decodeChunks(['foo\r', '\n', 'bar'], { flush: true })).toEqual(['foo', 'bar']); - }); - - test('single \\r', () => { - expect(decodeChunks(['foo\r', 'bar'], { flush: true })).toEqual(['foo', 'bar']); - }); - - test('double \\r', () => { - expect(decodeChunks(['foo\r', 'bar\r'], { flush: true })).toEqual(['foo', 'bar']); - expect(decodeChunks(['foo\r', '\r', 'bar'], { flush: true })).toEqual(['foo', '', 'bar']); - // implementation detail that we don't yield the single \r line until a new \r or \n is encountered - expect(decodeChunks(['foo\r', '\r', 'bar'], { flush: false })).toEqual(['foo']); - }); - - test('double \\r then \\r\\n', () => { - expect(decodeChunks(['foo\r', '\r', '\r', '\n', 'bar', '\n'])).toEqual(['foo', '', '', 'bar']); - expect(decodeChunks(['foo\n', '\n', '\n', 'bar', '\n'])).toEqual(['foo', '', '', 'bar']); - }); - - test('double newline', () => { - expect(decodeChunks(['foo\n\nbar'], { flush: true })).toEqual(['foo', '', 'bar']); - expect(decodeChunks(['foo', '\n', '\nbar'], { flush: true })).toEqual(['foo', '', 'bar']); - expect(decodeChunks(['foo\n', '\n', 'bar'], { flush: true })).toEqual(['foo', '', 'bar']); - expect(decodeChunks(['foo', '\n', '\n', 'bar'], { flush: true })).toEqual(['foo', '', 'bar']); - }); - - test('multi-byte characters across chunks', () => { - const decoder = new LineDecoder(); - - // bytes taken from the string 'известни' and arbitrarily split - // so that some multi-byte characters span multiple chunks - expect(decoder.decode(new Uint8Array([0xd0]))).toHaveLength(0); - expect(decoder.decode(new Uint8Array([0xb8, 0xd0, 0xb7, 0xd0]))).toHaveLength(0); - expect( - decoder.decode(new Uint8Array([0xb2, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0xd0, 0xbd, 0xd0, 0xb8])), - ).toHaveLength(0); - - const decoded = decoder.decode(new Uint8Array([0xa])); - expect(decoded).toEqual(['известни']); - }); - - test('flushing trailing newlines', () => { - expect(decodeChunks(['foo\n', '\nbar'], { flush: true })).toEqual(['foo', '', 'bar']); - }); - - test('flushing empty buffer', () => { - expect(decodeChunks([], { flush: true })).toEqual([]); - }); -}); - -describe('findDoubleNewlineIndex', () => { - test('finds \\n\\n', () => { - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\n\nbar'))).toBe(5); - expect(findDoubleNewlineIndex(new TextEncoder().encode('\n\nbar'))).toBe(2); - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\n\n'))).toBe(5); - expect(findDoubleNewlineIndex(new TextEncoder().encode('\n\n'))).toBe(2); - }); - - test('finds \\r\\r', () => { - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\rbar'))).toBe(5); - expect(findDoubleNewlineIndex(new TextEncoder().encode('\r\rbar'))).toBe(2); - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\r'))).toBe(5); - expect(findDoubleNewlineIndex(new TextEncoder().encode('\r\r'))).toBe(2); - }); - - test('finds \\r\\n\\r\\n', () => { - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\n\r\nbar'))).toBe(7); - expect(findDoubleNewlineIndex(new TextEncoder().encode('\r\n\r\nbar'))).toBe(4); - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\n\r\n'))).toBe(7); - expect(findDoubleNewlineIndex(new TextEncoder().encode('\r\n\r\n'))).toBe(4); - }); - - test('returns -1 when no double newline found', () => { - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\nbar'))).toBe(-1); - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\rbar'))).toBe(-1); - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\nbar'))).toBe(-1); - expect(findDoubleNewlineIndex(new TextEncoder().encode(''))).toBe(-1); - }); - - test('handles incomplete patterns', () => { - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\n\r'))).toBe(-1); - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\n'))).toBe(-1); - }); -}); diff --git a/tests/streaming.test.ts b/tests/streaming.test.ts deleted file mode 100644 index 068d3af..0000000 --- a/tests/streaming.test.ts +++ /dev/null @@ -1,219 +0,0 @@ -import assert from 'assert'; -import { _iterSSEMessages } from 'parallel-web/core/streaming'; -import { ReadableStreamFrom } from 'parallel-web/internal/shims'; - -describe('streaming decoding', () => { - test('basic', async () => { - async function* body(): AsyncGenerator { - yield Buffer.from('event: completion\n'); - yield Buffer.from('data: {"foo":true}\n'); - yield Buffer.from('\n'); - } - - const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ - Symbol.asyncIterator - ](); - - let event = await stream.next(); - assert(event.value); - expect(JSON.parse(event.value.data)).toEqual({ foo: true }); - - event = await stream.next(); - expect(event.done).toBeTruthy(); - }); - - test('data without event', async () => { - async function* body(): AsyncGenerator { - yield Buffer.from('data: {"foo":true}\n'); - yield Buffer.from('\n'); - } - - const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ - Symbol.asyncIterator - ](); - - let event = await stream.next(); - assert(event.value); - expect(event.value.event).toBeNull(); - expect(JSON.parse(event.value.data)).toEqual({ foo: true }); - - event = await stream.next(); - expect(event.done).toBeTruthy(); - }); - - test('event without data', async () => { - async function* body(): AsyncGenerator { - yield Buffer.from('event: foo\n'); - yield Buffer.from('\n'); - } - - const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ - Symbol.asyncIterator - ](); - - let event = await stream.next(); - assert(event.value); - expect(event.value.event).toEqual('foo'); - expect(event.value.data).toEqual(''); - - event = await stream.next(); - expect(event.done).toBeTruthy(); - }); - - test('multiple events', async () => { - async function* body(): AsyncGenerator { - yield Buffer.from('event: foo\n'); - yield Buffer.from('\n'); - yield Buffer.from('event: ping\n'); - yield Buffer.from('\n'); - } - - const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ - Symbol.asyncIterator - ](); - - let event = await stream.next(); - assert(event.value); - expect(event.value.event).toEqual('foo'); - expect(event.value.data).toEqual(''); - - event = await stream.next(); - assert(event.value); - expect(event.value.event).toEqual('ping'); - expect(event.value.data).toEqual(''); - - event = await stream.next(); - expect(event.done).toBeTruthy(); - }); - - test('multiple events with data', async () => { - async function* body(): AsyncGenerator { - yield Buffer.from('event: foo\n'); - yield Buffer.from('data: {"foo":true}\n'); - yield Buffer.from('\n'); - yield Buffer.from('event: ping\n'); - yield Buffer.from('data: {"bar":false}\n'); - yield Buffer.from('\n'); - } - - const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ - Symbol.asyncIterator - ](); - - let event = await stream.next(); - assert(event.value); - expect(event.value.event).toEqual('foo'); - expect(JSON.parse(event.value.data)).toEqual({ foo: true }); - - event = await stream.next(); - assert(event.value); - expect(event.value.event).toEqual('ping'); - expect(JSON.parse(event.value.data)).toEqual({ bar: false }); - - event = await stream.next(); - expect(event.done).toBeTruthy(); - }); - - test('multiple data lines with empty line', async () => { - async function* body(): AsyncGenerator { - yield Buffer.from('event: ping\n'); - yield Buffer.from('data: {\n'); - yield Buffer.from('data: "foo":\n'); - yield Buffer.from('data: \n'); - yield Buffer.from('data:\n'); - yield Buffer.from('data: true}\n'); - yield Buffer.from('\n\n'); - } - - const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ - Symbol.asyncIterator - ](); - - let event = await stream.next(); - assert(event.value); - expect(event.value.event).toEqual('ping'); - expect(JSON.parse(event.value.data)).toEqual({ foo: true }); - expect(event.value.data).toEqual('{\n"foo":\n\n\ntrue}'); - - event = await stream.next(); - expect(event.done).toBeTruthy(); - }); - - test('data json escaped double new line', async () => { - async function* body(): AsyncGenerator { - yield Buffer.from('event: ping\n'); - yield Buffer.from('data: {"foo": "my long\\n\\ncontent"}'); - yield Buffer.from('\n\n'); - } - - const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ - Symbol.asyncIterator - ](); - - let event = await stream.next(); - assert(event.value); - expect(event.value.event).toEqual('ping'); - expect(JSON.parse(event.value.data)).toEqual({ foo: 'my long\n\ncontent' }); - - event = await stream.next(); - expect(event.done).toBeTruthy(); - }); - - test('special new line characters', async () => { - async function* body(): AsyncGenerator { - yield Buffer.from('data: {"content": "culpa "}\n'); - yield Buffer.from('\n'); - yield Buffer.from('data: {"content": "'); - yield Buffer.from([0xe2, 0x80, 0xa8]); - yield Buffer.from('"}\n'); - yield Buffer.from('\n'); - yield Buffer.from('data: {"content": "foo"}\n'); - yield Buffer.from('\n'); - } - - const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ - Symbol.asyncIterator - ](); - - let event = await stream.next(); - assert(event.value); - expect(JSON.parse(event.value.data)).toEqual({ content: 'culpa ' }); - - event = await stream.next(); - assert(event.value); - expect(JSON.parse(event.value.data)).toEqual({ content: Buffer.from([0xe2, 0x80, 0xa8]).toString() }); - - event = await stream.next(); - assert(event.value); - expect(JSON.parse(event.value.data)).toEqual({ content: 'foo' }); - - event = await stream.next(); - expect(event.done).toBeTruthy(); - }); - - test('multi-byte characters across chunks', async () => { - async function* body(): AsyncGenerator { - yield Buffer.from('event: completion\n'); - yield Buffer.from('data: {"content": "'); - // bytes taken from the string 'известни' and arbitrarily split - // so that some multi-byte characters span multiple chunks - yield Buffer.from([0xd0]); - yield Buffer.from([0xb8, 0xd0, 0xb7, 0xd0]); - yield Buffer.from([0xb2, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0xd0, 0xbd, 0xd0, 0xb8]); - yield Buffer.from('"}\n'); - yield Buffer.from('\n'); - } - - const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ - Symbol.asyncIterator - ](); - - let event = await stream.next(); - assert(event.value); - expect(event.value.event).toEqual('completion'); - expect(JSON.parse(event.value.data)).toEqual({ content: 'известни' }); - - event = await stream.next(); - expect(event.done).toBeTruthy(); - }); -}); From 7a985fbf420eed89c87940cef40efb650b554c9b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 31 Aug 2025 21:59:16 +0000 Subject: [PATCH 2/4] feat(api): update via SDK Studio --- .stats.yml | 8 +- README.md | 22 +- api.md | 48 ++ scripts/detect-breaking-changes | 8 +- src/client.ts | 8 + src/core/streaming.ts | 315 ++++++++++++ src/internal/decoders/line.ts | 135 +++++ src/internal/parse.ts | 14 + src/internal/request-options.ts | 2 + src/resources/beta/beta.ts | 146 +++++- src/resources/beta/index.ts | 27 +- src/resources/beta/task-group.ts | 267 +++++++++- src/resources/beta/task-run.ts | 514 +++++++++++++++++++- src/resources/index.ts | 3 + src/resources/shared.ts | 44 +- src/resources/task-run.ts | 241 +++++---- src/streaming.ts | 2 + tests/api-resources/beta/beta.test.ts | 21 + tests/api-resources/beta/task-group.test.ts | 126 +++++ tests/api-resources/beta/task-run.test.ts | 80 +++ tests/api-resources/task-run.test.ts | 37 +- tests/internal/decoders/line.test.ts | 128 +++++ tests/streaming.test.ts | 219 +++++++++ 23 files changed, 2273 insertions(+), 142 deletions(-) create mode 100644 src/core/streaming.ts create mode 100644 src/internal/decoders/line.ts create mode 100644 src/streaming.ts create mode 100644 tests/api-resources/beta/beta.test.ts create mode 100644 tests/api-resources/beta/task-group.test.ts create mode 100644 tests/api-resources/beta/task-run.test.ts create mode 100644 tests/internal/decoders/line.test.ts create mode 100644 tests/streaming.test.ts diff --git a/.stats.yml b/.stats.yml index c703e97..7c4f552 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 3 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/parallel-web%2Fparallel-sdk-ff0d5939e135b67b3448abf72d8bb0f9a574194337c7c7192453781347a9601d.yml -openapi_spec_hash: f3ce85349af6273a671d3d2781c4c877 -config_hash: 284b51e02bda8519b1f21bb67f1809e0 +configured_endpoints: 12 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/parallel-web%2Fparallel-sdk-1aeb1c81a84999f2d27ca9e86b041d74b892926bed126dc9b0f3cff4d7b26963.yml +openapi_spec_hash: 6280f6c6fb537f7c9ac5cc33ee2e433d +config_hash: 451edf5a87ae14248aa336ffc08d216f diff --git a/README.md b/README.md index 6dc066d..20d1935 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,10 @@ const client = new Parallel({ apiKey: process.env['PARALLEL_API_KEY'], // This is the default and can be omitted }); -const taskRun = await client.taskRun.create({ input: 'France (2023)', processor: 'processor' }); +const taskRun = await client.taskRun.create({ + input: 'What was the GDP of France in 2023?', + processor: 'base', +}); console.log(taskRun.run_id); ``` @@ -46,7 +49,10 @@ const client = new Parallel({ apiKey: process.env['PARALLEL_API_KEY'], // This is the default and can be omitted }); -const params: Parallel.TaskRunCreateParams = { input: 'France (2023)', processor: 'processor' }; +const params: Parallel.TaskRunCreateParams = { + input: 'What was the GDP of France in 2023?', + processor: 'base', +}; const taskRun: Parallel.TaskRun = await client.taskRun.create(params); ``` @@ -61,7 +67,7 @@ a subclass of `APIError` will be thrown: ```ts const taskRun = await client.taskRun - .create({ input: 'France (2023)', processor: 'processor' }) + .create({ input: 'What was the GDP of France in 2023?', processor: 'base' }) .catch(async (err) => { if (err instanceof Parallel.APIError) { console.log(err.status); // 400 @@ -102,7 +108,7 @@ const client = new Parallel({ }); // Or, configure per-request: -await client.taskRun.create({ input: 'France (2023)', processor: 'processor' }, { +await client.taskRun.create({ input: 'What was the GDP of France in 2023?', processor: 'base' }, { maxRetries: 5, }); ``` @@ -119,7 +125,7 @@ const client = new Parallel({ }); // Override per-request: -await client.taskRun.create({ input: 'France (2023)', processor: 'processor' }, { +await client.taskRun.create({ input: 'What was the GDP of France in 2023?', processor: 'base' }, { timeout: 5 * 1000, }); ``` @@ -142,12 +148,14 @@ Unlike `.asResponse()` this method consumes the body, returning once it is parse ```ts const client = new Parallel(); -const response = await client.taskRun.create({ input: 'France (2023)', processor: 'processor' }).asResponse(); +const response = await client.taskRun + .create({ input: 'What was the GDP of France in 2023?', processor: 'base' }) + .asResponse(); console.log(response.headers.get('X-My-Header')); console.log(response.statusText); // access the underlying Response object const { data: taskRun, response: raw } = await client.taskRun - .create({ input: 'France (2023)', processor: 'processor' }) + .create({ input: 'What was the GDP of France in 2023?', processor: 'base' }) .withResponse(); console.log(raw.headers.get('X-My-Header')); console.log(taskRun.run_id); diff --git a/api.md b/api.md index c20960c..b41a73e 100644 --- a/api.md +++ b/api.md @@ -4,13 +4,18 @@ Types: - ErrorObject - ErrorResponse +- SourcePolicy +- Warning # TaskRun Types: +- AutoSchema - Citation +- FieldBasis - JsonSchema +- RunInput - TaskRun - TaskRunJsonOutput - TaskRunResult @@ -26,6 +31,49 @@ Methods: # Beta +Types: + +- SearchResult +- WebSearchResult + +Methods: + +- client.beta.search({ ...params }) -> SearchResult + ## TaskRun +Types: + +- BetaRunInput +- BetaTaskRunResult +- ErrorEvent +- McpServer +- McpToolCall +- ParallelBeta +- TaskRunEvent +- Webhook +- TaskRunEventsResponse + +Methods: + +- client.beta.taskRun.create({ ...params }) -> TaskRun +- client.beta.taskRun.events(runID) -> TaskRunEventsResponse +- client.beta.taskRun.result(runID, { ...params }) -> BetaTaskRunResult + ## TaskGroup + +Types: + +- TaskGroup +- TaskGroupRunResponse +- TaskGroupStatus +- TaskGroupEventsResponse +- TaskGroupGetRunsResponse + +Methods: + +- client.beta.taskGroup.create({ ...params }) -> TaskGroup +- client.beta.taskGroup.retrieve(taskGroupID) -> TaskGroup +- client.beta.taskGroup.addRuns(taskGroupID, { ...params }) -> TaskGroupRunResponse +- client.beta.taskGroup.events(taskGroupID, { ...params }) -> TaskGroupEventsResponse +- client.beta.taskGroup.getRuns(taskGroupID, { ...params }) -> TaskGroupGetRunsResponse diff --git a/scripts/detect-breaking-changes b/scripts/detect-breaking-changes index 69dba46..52c6340 100755 --- a/scripts/detect-breaking-changes +++ b/scripts/detect-breaking-changes @@ -6,7 +6,13 @@ cd "$(dirname "$0")/.." echo "==> Detecting breaking changes" -TEST_PATHS=( tests/api-resources/task-run.test.ts tests/index.test.ts ) +TEST_PATHS=( + tests/api-resources/task-run.test.ts + tests/api-resources/beta/beta.test.ts + tests/api-resources/beta/task-run.test.ts + tests/api-resources/beta/task-group.test.ts + tests/index.test.ts +) for PATHSPEC in "${TEST_PATHS[@]}"; do # Try to check out previous versions of the test files diff --git a/src/client.ts b/src/client.ts index 044fd17..4aa25c1 100644 --- a/src/client.ts +++ b/src/client.ts @@ -17,8 +17,11 @@ import * as Uploads from './core/uploads'; import * as API from './resources/index'; import { APIPromise } from './core/api-promise'; import { + AutoSchema, Citation, + FieldBasis, JsonSchema, + RunInput, TaskRun, TaskRunCreateParams, TaskRunJsonOutput, @@ -737,8 +740,11 @@ export declare namespace Parallel { export { type TaskRun as TaskRun, + type AutoSchema as AutoSchema, type Citation as Citation, + type FieldBasis as FieldBasis, type JsonSchema as JsonSchema, + type RunInput as RunInput, type TaskRunJsonOutput as TaskRunJsonOutput, type TaskRunResult as TaskRunResult, type TaskRunTextOutput as TaskRunTextOutput, @@ -752,4 +758,6 @@ export declare namespace Parallel { export type ErrorObject = API.ErrorObject; export type ErrorResponse = API.ErrorResponse; + export type SourcePolicy = API.SourcePolicy; + export type Warning = API.Warning; } diff --git a/src/core/streaming.ts b/src/core/streaming.ts new file mode 100644 index 0000000..57924d1 --- /dev/null +++ b/src/core/streaming.ts @@ -0,0 +1,315 @@ +import { ParallelError } 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 { encodeUTF8 } from '../internal/utils/bytes'; +import { loggerFor } from '../internal/utils/log'; +import type { Parallel } from '../client'; + +type Bytes = string | ArrayBuffer | Uint8Array | null | undefined; + +export type ServerSentEvent = { + event: string | null; + data: string; + raw: string[]; +}; + +export class Stream implements AsyncIterable { + controller: AbortController; + #client: Parallel | undefined; + + constructor( + private iterator: () => AsyncIterator, + controller: AbortController, + client?: Parallel, + ) { + this.controller = controller; + this.#client = client; + } + + static fromSSEResponse( + response: Response, + controller: AbortController, + client?: Parallel, + ): Stream { + let consumed = false; + const logger = client ? loggerFor(client) : console; + + async function* iterator(): AsyncIterator { + if (consumed) { + throw new ParallelError('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)) { + try { + yield JSON.parse(sse.data); + } catch (e) { + logger.error(`Could not parse message into JSON:`, sse.data); + logger.error(`From chunk:`, sse.raw); + throw e; + } + } + 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( + readableStream: ReadableStream, + controller: AbortController, + client?: Parallel, + ): Stream { + let consumed = false; + + async function* iterLines(): AsyncGenerator { + const lineDecoder = new LineDecoder(); + + const iter = ReadableStreamToAsyncIterable(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 { + if (consumed) { + throw new ParallelError('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); + } + 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 { + return this.iterator(); + } + + /** + * Splits the stream into two streams which can be + * independently read from at different speeds. + */ + tee(): [Stream, Stream] { + const left: Array>> = []; + const right: Array>> = []; + const iterator = this.iterator(); + + const teeIterator = (queue: Array>>): AsyncIterator => { + 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; + + 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 { + if (!response.body) { + controller.abort(); + if ( + typeof (globalThis as any).navigator !== 'undefined' && + (globalThis as any).navigator.product === 'ReactNative' + ) { + throw new ParallelError( + `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 ParallelError(`Attempted to iterate over a response with no body`); + } + + const sseDecoder = new SSEDecoder(); + const lineDecoder = new LineDecoder(); + + const iter = ReadableStreamToAsyncIterable(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): AsyncGenerator { + 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, '', '']; +} diff --git a/src/internal/decoders/line.ts b/src/internal/decoders/line.ts new file mode 100644 index 0000000..b3bfa97 --- /dev/null +++ b/src/internal/decoders/line.ts @@ -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; +} diff --git a/src/internal/parse.ts b/src/internal/parse.ts index a17b213..914d001 100644 --- a/src/internal/parse.ts +++ b/src/internal/parse.ts @@ -1,6 +1,7 @@ // 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 Parallel } from '../client'; import { formatRequestDetails, loggerFor } from './utils/log'; @@ -16,6 +17,19 @@ export type APIResponseProps = { export async function defaultParseResponse(client: Parallel, props: APIResponseProps): Promise { 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` + + if (props.options.__streamClass) { + return props.options.__streamClass.fromSSEResponse(response, props.controller, client) as any; + } + + return Stream.fromSSEResponse(response, props.controller, client) as any; + } + // fetch refuses to read the body when the status code is 204. if (response.status === 204) { return null as T; diff --git a/src/internal/request-options.ts b/src/internal/request-options.ts index 2aabf9a..56765e5 100644 --- a/src/internal/request-options.ts +++ b/src/internal/request-options.ts @@ -3,6 +3,7 @@ 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'; @@ -76,6 +77,7 @@ export type RequestOptions = { defaultBaseURL?: string | undefined; __binaryResponse?: boolean | undefined; + __streamClass?: typeof Stream; }; export type EncodedContent = { bodyHeaders: HeadersLike; body: BodyInit }; diff --git a/src/resources/beta/beta.ts b/src/resources/beta/beta.ts index 648f9c3..c0de3d7 100644 --- a/src/resources/beta/beta.ts +++ b/src/resources/beta/beta.ts @@ -1,21 +1,157 @@ // 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 TaskGroupAPI from './task-group'; -import { TaskGroup } from './task-group'; +import { + TaskGroup, + TaskGroupAddRunsParams, + TaskGroupCreateParams, + TaskGroupEventsParams, + TaskGroupEventsResponse, + TaskGroupGetRunsParams, + TaskGroupGetRunsResponse, + TaskGroupRunResponse, + TaskGroupStatus, +} from './task-group'; import * as TaskRunAPI from './task-run'; -import { TaskRun } from './task-run'; +import { + BetaRunInput, + BetaTaskRunResult, + ErrorEvent, + McpServer, + McpToolCall, + ParallelBeta, + TaskRun, + TaskRunCreateParams, + TaskRunEvent, + TaskRunEventsResponse, + TaskRunResultParams, + Webhook, +} from './task-run'; +import { APIPromise } from '../../core/api-promise'; +import { RequestOptions } from '../../internal/request-options'; export class Beta extends APIResource { taskRun: TaskRunAPI.TaskRun = new TaskRunAPI.TaskRun(this._client); taskGroup: TaskGroupAPI.TaskGroup = new TaskGroupAPI.TaskGroup(this._client); + + /** + * Searches the web. + */ + search(body: BetaSearchParams, options?: RequestOptions): APIPromise { + return this._client.post('/v1beta/search', { body, ...options }); + } +} + +/** + * Output for the Search API. + */ +export interface SearchResult { + /** + * A list of WebSearchResult objects, ordered by decreasing relevance. + */ + results: Array; + + /** + * Search ID. Example: `search_cad0a6d2-dec0-46bd-95ae-900527d880e7` + */ + search_id: string; +} + +/** + * A single search result from the web search API. + */ +export interface WebSearchResult { + /** + * Text excerpts from the search result which are relevant to the request. + */ + excerpts: Array; + + /** + * Title of the search result. + */ + title: string; + + /** + * URL associated with the search result. + */ + url: string; +} + +export interface BetaSearchParams { + /** + * Upper bound on the number of characters to include in excerpts for each search + * result. + */ + max_chars_per_result?: number | null; + + /** + * Upper bound on the number of results to return. May be limited by the processor. + * Defaults to 10 if not provided. + */ + max_results?: number | null; + + /** + * Natural-language description of what the web search is trying to find. May + * include guidance about preferred sources or freshness. At least one of objective + * or search_queries must be provided. + */ + objective?: string | null; + + /** + * Search processor. + */ + processor?: 'base' | 'pro'; + + /** + * Optional list of traditional keyword search queries to guide the search. May + * contain search operators. At least one of objective or search_queries must be + * provided. + */ + search_queries?: Array | null; + + /** + * Source policy for web search results. + * + * This policy governs which sources are allowed/disallowed in results. + */ + source_policy?: Shared.SourcePolicy | null; } Beta.TaskRun = TaskRun; -Beta.TaskGroup = TaskGroup; export declare namespace Beta { - export { TaskRun as TaskRun }; + export { + type SearchResult as SearchResult, + type WebSearchResult as WebSearchResult, + type BetaSearchParams as BetaSearchParams, + }; + + export { + TaskRun as TaskRun, + type BetaRunInput as BetaRunInput, + type BetaTaskRunResult as BetaTaskRunResult, + type ErrorEvent as ErrorEvent, + type McpServer as McpServer, + type McpToolCall as McpToolCall, + type ParallelBeta as ParallelBeta, + type TaskRunEvent as TaskRunEvent, + type Webhook as Webhook, + type TaskRunEventsResponse as TaskRunEventsResponse, + type TaskRunCreateParams as TaskRunCreateParams, + type TaskRunResultParams as TaskRunResultParams, + }; - export { TaskGroup as TaskGroup }; + export { + type TaskGroup as TaskGroup, + type TaskGroupRunResponse as TaskGroupRunResponse, + type TaskGroupStatus as TaskGroupStatus, + type TaskGroupEventsResponse as TaskGroupEventsResponse, + type TaskGroupGetRunsResponse as TaskGroupGetRunsResponse, + type TaskGroupCreateParams as TaskGroupCreateParams, + type TaskGroupAddRunsParams as TaskGroupAddRunsParams, + type TaskGroupEventsParams as TaskGroupEventsParams, + type TaskGroupGetRunsParams as TaskGroupGetRunsParams, + }; } diff --git a/src/resources/beta/index.ts b/src/resources/beta/index.ts index bf1f5d2..061e58c 100644 --- a/src/resources/beta/index.ts +++ b/src/resources/beta/index.ts @@ -1,5 +1,28 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. export { Beta } from './beta'; -export { TaskGroup } from './task-group'; -export { TaskRun } from './task-run'; +export { + TaskGroup, + type TaskGroupRunResponse, + type TaskGroupStatus, + type TaskGroupEventsResponse, + type TaskGroupGetRunsResponse, + type TaskGroupCreateParams, + type TaskGroupAddRunsParams, + type TaskGroupEventsParams, + type TaskGroupGetRunsParams, +} from './task-group'; +export { + TaskRun, + type BetaRunInput, + type BetaTaskRunResult, + type ErrorEvent, + type McpServer, + type McpToolCall, + type ParallelBeta, + type TaskRunEvent, + type Webhook, + type TaskRunEventsResponse, + type TaskRunCreateParams, + type TaskRunResultParams, +} from './task-run'; diff --git a/src/resources/beta/task-group.ts b/src/resources/beta/task-group.ts index bfdaad5..f47f308 100644 --- a/src/resources/beta/task-group.ts +++ b/src/resources/beta/task-group.ts @@ -1,5 +1,270 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../../core/resource'; +import * as TaskGroupAPI from './task-group'; +import * as TaskRunAPI from '../task-run'; +import * as BetaTaskRunAPI from './task-run'; +import { APIPromise } from '../../core/api-promise'; +import { Stream } from '../../core/streaming'; +import { buildHeaders } from '../../internal/headers'; +import { RequestOptions } from '../../internal/request-options'; +import { path } from '../../internal/utils/path'; -export class TaskGroup extends APIResource {} +export class TaskGroup extends APIResource { + /** + * Initiates a TaskGroup to group and track multiple runs. + */ + create(body: TaskGroupCreateParams, options?: RequestOptions): APIPromise { + return this._client.post('/v1beta/tasks/groups', { body, ...options }); + } + + /** + * Retrieves aggregated status across runs in a TaskGroup. + */ + retrieve(taskGroupID: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/v1beta/tasks/groups/${taskGroupID}`, options); + } + + /** + * Initiates multiple task runs within a TaskGroup. + */ + addRuns( + taskGroupID: string, + params: TaskGroupAddRunsParams, + options?: RequestOptions, + ): APIPromise { + const { betas, ...body } = params; + return this._client.post(path`/v1beta/tasks/groups/${taskGroupID}/runs`, { + body, + ...options, + headers: buildHeaders([ + { ...(betas?.toString() != null ? { 'parallel-beta': betas?.toString() } : undefined) }, + options?.headers, + ]), + }); + } + + /** + * Streams events from a TaskGroup: status updates and run completions. + * + * The connection will remain open for up to 10 minutes as long as at least one run + * in the TaskGroup is active. + */ + events( + taskGroupID: string, + query: TaskGroupEventsParams | undefined = {}, + options?: RequestOptions, + ): APIPromise> { + return this._client.get(path`/v1beta/tasks/groups/${taskGroupID}/events`, { + query, + ...options, + headers: buildHeaders([{ Accept: 'text/event-stream' }, options?.headers]), + stream: true, + }) as APIPromise>; + } + + /** + * Retrieves task runs in a TaskGroup and optionally their inputs and outputs. + */ + getRuns( + taskGroupID: string, + query: TaskGroupGetRunsParams | undefined = {}, + options?: RequestOptions, + ): APIPromise> { + return this._client.get(path`/v1beta/tasks/groups/${taskGroupID}/runs`, { + query, + ...options, + headers: buildHeaders([{ Accept: 'text/event-stream' }, options?.headers]), + stream: true, + }) as APIPromise>; + } +} + +/** + * Response object for a task group, including its status and metadata. + */ +export interface TaskGroup { + /** + * Timestamp of the creation of the group, as an RFC 3339 string. + */ + created_at: string | null; + + /** + * Status of a task group. + */ + status: TaskGroupStatus; + + /** + * ID of the group. + */ + taskgroup_id: string; + + /** + * User-provided metadata stored with the group. + */ + metadata?: { [key: string]: string | number | boolean } | null; +} + +/** + * Response from adding new task runs to a task group. + */ +export interface TaskGroupRunResponse { + /** + * Cursor for these runs in the event stream at + * taskgroup/events?last_event_id=. Empty for the first runs in the + * group. + */ + event_cursor: string | null; + + /** + * Cursor for these runs in the run stream at + * taskgroup/runs?last_event_id=. Empty for the first runs in the + * group. + */ + run_cursor: string | null; + + /** + * IDs of the newly created runs. + */ + run_ids: Array; + + /** + * Status of a task group. + */ + status: TaskGroupStatus; +} + +/** + * Status of a task group. + */ +export interface TaskGroupStatus { + /** + * True if at least one run in the group is currently active, i.e. status is one of + * {'cancelling', 'queued', 'running'}. + */ + is_active: boolean; + + /** + * Timestamp of the last status update to the group, as an RFC 3339 string. + */ + modified_at: string | null; + + /** + * Number of task runs in the group. + */ + num_task_runs: number; + + /** + * Human-readable status message for the group. + */ + status_message: string | null; + + /** + * Number of task runs with each status. + */ + task_run_status_counts: { [key: string]: number }; +} + +/** + * Event indicating an update to group status. + */ +export type TaskGroupEventsResponse = + | TaskGroupEventsResponse.TaskGroupStatusEvent + | BetaTaskRunAPI.TaskRunEvent + | BetaTaskRunAPI.ErrorEvent; + +export namespace TaskGroupEventsResponse { + /** + * Event indicating an update to group status. + */ + export interface TaskGroupStatusEvent { + /** + * Cursor to resume the event stream. + */ + event_id: string; + + /** + * Status of a task group. + */ + status: TaskGroupAPI.TaskGroupStatus; + + /** + * Event type; always 'task_group_status'. + */ + type: 'task_group_status'; + } +} + +/** + * Event when a task run transitions to a non-active status. + * + * May indicate completion, cancellation, or failure. + */ +export type TaskGroupGetRunsResponse = BetaTaskRunAPI.TaskRunEvent | BetaTaskRunAPI.ErrorEvent; + +export interface TaskGroupCreateParams { + /** + * User-provided metadata stored with the task group. + */ + metadata?: { [key: string]: string | number | boolean } | null; +} + +export interface TaskGroupAddRunsParams { + /** + * Body param: List of task runs to execute. + */ + inputs: Array; + + /** + * Body param: Specification for a task. + * + * Auto output schemas can be specified by setting `output_schema={"type":"auto"}`. + * Not specifying a TaskSpec is the same as setting an auto output schema. + * + * For convenience bare strings are also accepted as input or output schemas. + */ + default_task_spec?: TaskRunAPI.TaskSpec | null; + + /** + * Header param: Optional header to specify the beta version(s) to enable. + */ + betas?: Array; +} + +export interface TaskGroupEventsParams { + last_event_id?: string | null; + + timeout?: number | null; +} + +export interface TaskGroupGetRunsParams { + include_input?: boolean; + + include_output?: boolean; + + last_event_id?: string | null; + + status?: + | 'queued' + | 'action_required' + | 'running' + | 'completed' + | 'failed' + | 'cancelling' + | 'cancelled' + | null; +} + +export declare namespace TaskGroup { + export { + type TaskGroup as TaskGroup, + type TaskGroupRunResponse as TaskGroupRunResponse, + type TaskGroupStatus as TaskGroupStatus, + type TaskGroupEventsResponse as TaskGroupEventsResponse, + type TaskGroupGetRunsResponse as TaskGroupGetRunsResponse, + type TaskGroupCreateParams as TaskGroupCreateParams, + type TaskGroupAddRunsParams as TaskGroupAddRunsParams, + type TaskGroupEventsParams as TaskGroupEventsParams, + type TaskGroupGetRunsParams as TaskGroupGetRunsParams, + }; +} diff --git a/src/resources/beta/task-run.ts b/src/resources/beta/task-run.ts index e42a175..bfaf20f 100644 --- a/src/resources/beta/task-run.ts +++ b/src/resources/beta/task-run.ts @@ -1,5 +1,517 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../../core/resource'; +import * as BetaTaskRunAPI from './task-run'; +import * as Shared from '../shared'; +import * as TaskRunAPI from '../task-run'; +import { APIPromise } from '../../core/api-promise'; +import { Stream } from '../../core/streaming'; +import { buildHeaders } from '../../internal/headers'; +import { RequestOptions } from '../../internal/request-options'; +import { path } from '../../internal/utils/path'; -export class TaskRun extends APIResource {} +export class TaskRun extends APIResource { + /** + * Initiates a task run. + * + * Returns immediately with a run object in status 'queued'. + * + * Beta features can be enabled by setting the 'parallel-beta' header. + */ + create(params: TaskRunCreateParams, options?: RequestOptions): APIPromise { + const { betas, ...body } = params; + return this._client.post('/v1/tasks/runs?beta=true', { + body, + ...options, + headers: buildHeaders([ + { ...(betas?.toString() != null ? { 'parallel-beta': betas?.toString() } : undefined) }, + options?.headers, + ]), + }); + } + + /** + * Streams events for a task run. + * + * Returns a stream of events showing progress updates and state changes for the + * task run. + * + * For task runs that did not have enable_events set to true during creation, the + * frequency of events will be reduced. + */ + events(runID: string, options?: RequestOptions): APIPromise> { + return this._client.get(path`/v1beta/tasks/runs/${runID}/events`, { + ...options, + headers: buildHeaders([{ Accept: 'text/event-stream' }, options?.headers]), + stream: true, + }) as APIPromise>; + } + + /** + * Retrieves a run result by run_id, blocking until the run is completed. + */ + result( + runID: string, + params: TaskRunResultParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + const { betas, ...query } = params ?? {}; + return this._client.get(path`/v1/tasks/runs/${runID}/result?beta=true`, { + query, + ...options, + headers: buildHeaders([ + { ...(betas?.toString() != null ? { 'parallel-beta': betas?.toString() } : undefined) }, + options?.headers, + ]), + }); + } +} + +/** + * Task run input with additional beta fields. + */ +export interface BetaRunInput { + /** + * Input to the task, either text or a JSON object. + */ + input: string | { [key: string]: unknown }; + + /** + * Processor to use for the task. + */ + processor: string; + + /** + * Controls tracking of task run execution progress. When set to true, progress + * events are recorded and can be accessed via the + * [Task Run events](https://platform.parallel.ai/api-reference) endpoint. When + * false, no progress events are tracked. Note that progress tracking cannot be + * enabled after a run has been created. The flag is set to true by default for + * premium processors (pro and above). This feature is not available via the Python + * SDK. To enable this feature in your API requests, specify the `parallel-beta` + * header with `events-sse-2025-07-24` value. + */ + enable_events?: boolean | null; + + /** + * Optional list of MCP servers to use for the run. This feature is not available + * via the Python SDK. To enable this feature in your API requests, specify the + * `parallel-beta` header with `mcp-server-2025-07-17` value. + */ + mcp_servers?: Array | null; + + /** + * User-provided metadata stored with the run. Keys and values must be strings with + * a maximum length of 16 and 512 characters respectively. + */ + metadata?: { [key: string]: string | number | boolean } | null; + + /** + * Source policy for web search results. + * + * This policy governs which sources are allowed/disallowed in results. + */ + source_policy?: Shared.SourcePolicy | null; + + /** + * Specification for a task. + * + * Auto output schemas can be specified by setting `output_schema={"type":"auto"}`. + * Not specifying a TaskSpec is the same as setting an auto output schema. + * + * For convenience bare strings are also accepted as input or output schemas. + */ + task_spec?: TaskRunAPI.TaskSpec | null; + + /** + * Webhooks for Task Runs. + */ + webhook?: Webhook | null; +} + +/** + * Result of a beta task run. Available only if beta headers are specified. + */ +export interface BetaTaskRunResult { + /** + * Output from the task conforming to the output schema. + */ + output: BetaTaskRunResult.BetaTaskRunTextOutput | BetaTaskRunResult.BetaTaskRunJsonOutput; + + /** + * Status of a task run. + */ + run: TaskRunAPI.TaskRun; +} + +export namespace BetaTaskRunResult { + /** + * Output from a task that returns text. + */ + export interface BetaTaskRunTextOutput { + /** + * Basis for the output. + */ + basis: Array; + + /** + * Text output from the task. + */ + content: string; + + /** + * The type of output being returned, as determined by the output schema of the + * task spec. + */ + type: 'text'; + + /** + * Always None. + */ + beta_fields?: { [key: string]: unknown } | null; + + /** + * MCP tool calls made by the task. + */ + mcp_tool_calls?: Array | null; + } + + /** + * Output from a task that returns JSON. + */ + export interface BetaTaskRunJsonOutput { + /** + * Basis for the output. + */ + basis: Array; + + /** + * Output from the task as a native JSON object, as determined by the output schema + * of the task spec. + */ + content: { [key: string]: unknown }; + + /** + * The type of output being returned, as determined by the output schema of the + * task spec. + */ + type: 'json'; + + /** + * Always None. + */ + beta_fields?: { [key: string]: unknown } | null; + + /** + * MCP tool calls made by the task. + */ + mcp_tool_calls?: Array | null; + + /** + * Output schema for the Task Run. Populated only if the task was executed with an + * auto schema. + */ + output_schema?: { [key: string]: unknown } | null; + } +} + +/** + * Event indicating an error. + */ +export interface ErrorEvent { + /** + * An error message. + */ + error: Shared.ErrorObject; + + /** + * Event type; always 'error'. + */ + type: 'error'; +} + +/** + * MCP server configuration. + */ +export interface McpServer { + /** + * Name of the MCP server. + */ + name: string; + + /** + * URL of the MCP server. + */ + url: string; + + /** + * List of allowed tools for the MCP server. + */ + allowed_tools?: Array | null; + + /** + * Headers for the MCP server. + */ + headers?: { [key: string]: string } | null; + + /** + * Type of MCP server being configured. Always `url`. + */ + type?: 'url'; +} + +/** + * Result of an MCP tool call. + */ +export interface McpToolCall { + /** + * Arguments used to call the MCP tool. + */ + arguments: string; + + /** + * Name of the MCP server. + */ + server_name: string; + + /** + * Identifier for the tool call. + */ + tool_call_id: string; + + /** + * Name of the tool being called. + */ + tool_name: string; + + /** + * Output received from the tool call, if successful. + */ + content?: string | null; + + /** + * Error message if the tool call failed. + */ + error?: string | null; +} + +/** + * Model for the parallel-beta header. + */ +export type ParallelBeta = + | 'mcp-server-2025-07-17' + | 'events-sse-2025-07-24' + | 'webhook-2025-08-12' + | (string & {}); + +/** + * Event when a task run transitions to a non-active status. + * + * May indicate completion, cancellation, or failure. + */ +export interface TaskRunEvent { + /** + * Cursor to resume the event stream. Always empty for non Task Group runs. + */ + event_id: string | null; + + /** + * Status of a task run. + */ + run: TaskRunAPI.TaskRun; + + /** + * Event type; always 'task_run.state'. + */ + type: 'task_run.state'; + + /** + * Task run input with additional beta fields. + */ + input?: BetaRunInput | null; + + /** + * Output from the run; included only if requested and if status == `completed`. + */ + output?: TaskRunAPI.TaskRunTextOutput | TaskRunAPI.TaskRunJsonOutput | null; +} + +/** + * Webhooks for Task Runs. + */ +export interface Webhook { + /** + * URL for the webhook. + */ + url: string; + + /** + * Event types to send the webhook notifications for. + */ + event_types?: Array<'task_run.status'>; +} + +/** + * A progress update for a task run. + */ +export type TaskRunEventsResponse = + | TaskRunEventsResponse.TaskRunProgressStatsEvent + | TaskRunEventsResponse.TaskRunProgressMessageEvent + | TaskRunEvent + | ErrorEvent; + +export namespace TaskRunEventsResponse { + /** + * A progress update for a task run. + */ + export interface TaskRunProgressStatsEvent { + /** + * Source stats for a task run. + */ + source_stats: TaskRunProgressStatsEvent.SourceStats; + + /** + * Event type; always 'task_run.progress_stats'. + */ + type: 'task_run.progress_stats'; + } + + export namespace TaskRunProgressStatsEvent { + /** + * Source stats for a task run. + */ + export interface SourceStats { + /** + * Number of sources considered in processing the task. + */ + num_sources_considered: number | null; + + /** + * Number of sources read in processing the task. + */ + num_sources_read: number | null; + + /** + * A sample of URLs of sources read in processing the task. + */ + sources_read_sample: Array | null; + } + } + + /** + * A message for a task run progress update. + */ + export interface TaskRunProgressMessageEvent { + /** + * Progress update message. + */ + message: string; + + /** + * Timestamp of the message. + */ + timestamp: string | null; + + /** + * Event type; always starts with 'task_run.progress_msg'. + */ + type: + | 'task_run.progress_msg.plan' + | 'task_run.progress_msg.search' + | 'task_run.progress_msg.result' + | 'task_run.progress_msg.tool_call' + | 'task_run.progress_msg.exec_status'; + } +} + +export interface TaskRunCreateParams { + /** + * Body param: Input to the task, either text or a JSON object. + */ + input: string | { [key: string]: unknown }; + + /** + * Body param: Processor to use for the task. + */ + processor: string; + + /** + * Body param: Controls tracking of task run execution progress. When set to true, + * progress events are recorded and can be accessed via the + * [Task Run events](https://platform.parallel.ai/api-reference) endpoint. When + * false, no progress events are tracked. Note that progress tracking cannot be + * enabled after a run has been created. The flag is set to true by default for + * premium processors (pro and above). This feature is not available via the Python + * SDK. To enable this feature in your API requests, specify the `parallel-beta` + * header with `events-sse-2025-07-24` value. + */ + enable_events?: boolean | null; + + /** + * Body param: Optional list of MCP servers to use for the run. This feature is not + * available via the Python SDK. To enable this feature in your API requests, + * specify the `parallel-beta` header with `mcp-server-2025-07-17` value. + */ + mcp_servers?: Array | null; + + /** + * Body param: User-provided metadata stored with the run. Keys and values must be + * strings with a maximum length of 16 and 512 characters respectively. + */ + metadata?: { [key: string]: string | number | boolean } | null; + + /** + * Body param: Source policy for web search results. + * + * This policy governs which sources are allowed/disallowed in results. + */ + source_policy?: Shared.SourcePolicy | null; + + /** + * Body param: Specification for a task. + * + * Auto output schemas can be specified by setting `output_schema={"type":"auto"}`. + * Not specifying a TaskSpec is the same as setting an auto output schema. + * + * For convenience bare strings are also accepted as input or output schemas. + */ + task_spec?: TaskRunAPI.TaskSpec | null; + + /** + * Body param: Webhooks for Task Runs. + */ + webhook?: Webhook | null; + + /** + * Header param: Optional header to specify the beta version(s) to enable. + */ + betas?: Array; +} + +export interface TaskRunResultParams { + /** + * Query param: + */ + timeout?: number; + + /** + * Header param: Optional header to specify the beta version(s) to enable. + */ + betas?: Array; +} + +export declare namespace TaskRun { + export { + type BetaRunInput as BetaRunInput, + type BetaTaskRunResult as BetaTaskRunResult, + type ErrorEvent as ErrorEvent, + type McpServer as McpServer, + type McpToolCall as McpToolCall, + type ParallelBeta as ParallelBeta, + type TaskRunEvent as TaskRunEvent, + type Webhook as Webhook, + type TaskRunEventsResponse as TaskRunEventsResponse, + type TaskRunCreateParams as TaskRunCreateParams, + type TaskRunResultParams as TaskRunResultParams, + }; +} diff --git a/src/resources/index.ts b/src/resources/index.ts index 911d9ca..709471e 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -4,8 +4,11 @@ export * from './shared'; export { Beta } from './beta/beta'; export { TaskRun, + type AutoSchema, type Citation, + type FieldBasis, type JsonSchema, + type RunInput, type TaskRunJsonOutput, type TaskRunResult, type TaskRunTextOutput, diff --git a/src/resources/shared.ts b/src/resources/shared.ts index 0ec2535..5bb1c1a 100644 --- a/src/resources/shared.ts +++ b/src/resources/shared.ts @@ -17,7 +17,7 @@ export interface ErrorObject { /** * Optional detail supporting the error. */ - detail?: unknown | null; + detail?: { [key: string]: unknown } | null; } /** @@ -32,5 +32,45 @@ export interface ErrorResponse { /** * Always 'error'. */ - type?: 'error'; + type: 'error'; +} + +/** + * Source policy for web search results. + * + * This policy governs which sources are allowed/disallowed in results. + */ +export interface SourcePolicy { + /** + * List of domains to exclude from results. If specified, sources from these + * domains will be excluded. + */ + exclude_domains?: Array; + + /** + * List of domains to restrict the results to. If specified, only sources from + * these domains will be included. + */ + include_domains?: Array; +} + +/** + * Human-readable message for a task. + */ +export interface Warning { + /** + * Human-readable message. + */ + message: string; + + /** + * Type of warning. Note that adding new warning types is considered a + * backward-compatible change. + */ + type: 'spec_validation_warning' | 'input_validation_warning' | 'warning'; + + /** + * Optional detail supporting the warning. + */ + detail?: { [key: string]: unknown } | null; } diff --git a/src/resources/task-run.ts b/src/resources/task-run.ts index bac8e38..6cada92 100644 --- a/src/resources/task-run.ts +++ b/src/resources/task-run.ts @@ -1,28 +1,34 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../core/resource'; -import * as TaskRunAPI from './task-run'; +import * as Shared from './shared'; import { APIPromise } from '../core/api-promise'; import { RequestOptions } from '../internal/request-options'; import { path } from '../internal/utils/path'; export class TaskRun extends APIResource { /** - * Initiates a single task run. + * Initiates a task run. + * + * Returns immediately with a run object in status 'queued'. + * + * Beta features can be enabled by setting the 'parallel-beta' header. */ create(body: TaskRunCreateParams, options?: RequestOptions): APIPromise { return this._client.post('/v1/tasks/runs', { body, ...options }); } /** - * Retrieves a run by run_id. + * Retrieves run status by run_id. + * + * The run result is available from the `/result` endpoint. */ retrieve(runID: string, options?: RequestOptions): APIPromise { return this._client.get(path`/v1/tasks/runs/${runID}`, options); } /** - * Retrieves a run by run_id, blocking until the run is completed. + * Retrieves a run result by run_id, blocking until the run is completed. */ result( runID: string, @@ -33,6 +39,16 @@ export class TaskRun extends APIResource { } } +/** + * Auto schema for a task input or output. + */ +export interface AutoSchema { + /** + * The type of schema being defined. Always `auto`. + */ + type?: 'auto'; +} + /** * A citation for a task output. */ @@ -54,6 +70,32 @@ export interface Citation { title?: string | null; } +/** + * Citations and reasoning supporting one field of a task output. + */ +export interface FieldBasis { + /** + * Name of the output field. + */ + field: string; + + /** + * Reasoning for the output field. + */ + reasoning: string; + + /** + * List of citations supporting the output field. + */ + citations?: Array; + + /** + * Confidence level for the output field. Only certain processors provide + * confidence levels. + */ + confidence?: string | null; +} + /** * JSON schema for a task input or output. */ @@ -61,7 +103,7 @@ export interface JsonSchema { /** * A JSON Schema object. Only a subset of JSON Schema is supported. */ - json_schema: unknown; + json_schema: { [key: string]: unknown }; /** * The type of schema being defined. Always `json`. @@ -70,7 +112,45 @@ export interface JsonSchema { } /** - * Status of a task. + * Request to run a task. + */ +export interface RunInput { + /** + * Input to the task, either text or a JSON object. + */ + input: string | { [key: string]: unknown }; + + /** + * Processor to use for the task. + */ + processor: string; + + /** + * User-provided metadata stored with the run. Keys and values must be strings with + * a maximum length of 16 and 512 characters respectively. + */ + metadata?: { [key: string]: string | number | boolean } | null; + + /** + * Source policy for web search results. + * + * This policy governs which sources are allowed/disallowed in results. + */ + source_policy?: Shared.SourcePolicy | null; + + /** + * Specification for a task. + * + * Auto output schemas can be specified by setting `output_schema={"type":"auto"}`. + * Not specifying a TaskSpec is the same as setting an auto output schema. + * + * For convenience bare strings are also accepted as input or output schemas. + */ + task_spec?: TaskSpec | null; +} + +/** + * Status of a task run. */ export interface TaskRun { /** @@ -79,8 +159,8 @@ export interface TaskRun { created_at: string | null; /** - * Whether the run is currently active; i.e. status is one of {'running', 'queued', - * 'cancelling'}. + * Whether the run is currently active, i.e. status is one of {'cancelling', + * 'queued', 'running'}. */ is_active: boolean; @@ -104,88 +184,67 @@ export interface TaskRun { */ status: 'queued' | 'action_required' | 'running' | 'completed' | 'failed' | 'cancelling' | 'cancelled'; + /** + * An error message. + */ + error?: Shared.ErrorObject | null; + /** * User-provided metadata stored with the run. */ metadata?: { [key: string]: string | number | boolean } | null; /** - * Warnings for the run. + * ID of the taskgroup to which the run belongs. */ - warnings?: Array | null; -} + taskgroup_id?: string | null; -export namespace TaskRun { /** - * Human-readable message for a task. + * Warnings for the run, if any. */ - export interface Warning { - /** - * Human-readable message. - */ - message: string; - - /** - * Type of warning. Note that adding new warning types is considered a - * backward-compatible change. - */ - type: string; - - /** - * Optional detail supporting the warning. - */ - detail?: unknown | null; - } + warnings?: Array | null; } /** - * Output from a task that returns text. + * Output from a task that returns JSON. */ export interface TaskRunJsonOutput { /** * Basis for each top-level field in the JSON output. */ - basis: Array; + basis: Array; /** * Output from the task as a native JSON object, as determined by the output schema * of the task spec. */ - content: unknown; + content: { [key: string]: unknown }; /** * The type of output being returned, as determined by the output schema of the * task spec. */ type: 'json'; -} -export namespace TaskRunJsonOutput { /** - * Citations and reasoning supporting one field of a task output. + * Additional fields from beta features used in this task run. When beta features + * are specified during both task run creation and result retrieval, this field + * will be empty and instead the relevant beta attributes will be directly included + * in the `BetaTaskRunJsonOutput` or corresponding output type. However, if beta + * features were specified during task run creation but not during result + * retrieval, this field will contain the dump of fields from those beta features. + * Each key represents the beta feature version (one amongst parallel-beta headers) + * and the values correspond to the beta feature attributes, if any. For now, only + * MCP server beta features have attributes. For example, + * `{mcp-server-2025-07-17: [{'server_name':'mcp_server', 'tool_call_id': 'tc_123', ...}]}}` */ - export interface Basis { - /** - * Name of the output field. - */ - field: string; - - /** - * Reasoning for the output field. - */ - reasoning: string; - - /** - * List of citations supporting the output field. - */ - citations?: Array; + beta_fields?: { [key: string]: unknown } | null; - /** - * Confidence level for the output field. Only certain processors provide - * confidence levels. - */ - confidence?: string | null; - } + /** + * Output schema for the Task Run. Populated only if the task was executed with an + * auto schema. + */ + output_schema?: { [key: string]: unknown } | null; } /** @@ -198,7 +257,7 @@ export interface TaskRunResult { output: TaskRunTextOutput | TaskRunJsonOutput; /** - * Status of a task. + * Status of a task run. */ run: TaskRun; } @@ -210,7 +269,7 @@ export interface TaskRunTextOutput { /** * Basis for the output. The basis has a single field 'output'. */ - basis: Array; + basis: Array; /** * Text output from the task. @@ -222,41 +281,29 @@ export interface TaskRunTextOutput { * task spec. */ type: 'text'; -} -export namespace TaskRunTextOutput { /** - * Citations and reasoning supporting one field of a task output. + * Additional fields from beta features used in this task run. When beta features + * are specified during both task run creation and result retrieval, this field + * will be empty and instead the relevant beta attributes will be directly included + * in the `BetaTaskRunJsonOutput` or corresponding output type. However, if beta + * features were specified during task run creation but not during result + * retrieval, this field will contain the dump of fields from those beta features. + * Each key represents the beta feature version (one amongst parallel-beta headers) + * and the values correspond to the beta feature attributes, if any. For now, only + * MCP server beta features have attributes. For example, + * `{mcp-server-2025-07-17: [{'server_name':'mcp_server', 'tool_call_id': 'tc_123', ...}]}}` */ - export interface Basis { - /** - * Name of the output field. - */ - field: string; - - /** - * Reasoning for the output field. - */ - reasoning: string; - - /** - * List of citations supporting the output field. - */ - citations?: Array; - - /** - * Confidence level for the output field. Only certain processors provide - * confidence levels. - */ - confidence?: string | null; - } + beta_fields?: { [key: string]: unknown } | null; } /** * Specification for a task. * - * For convenience we allow bare strings as input or output schemas, which is - * equivalent to a text schema with the same description. + * Auto output schemas can be specified by setting `output_schema={"type":"auto"}`. + * Not specifying a TaskSpec is the same as setting an auto output schema. + * + * For convenience bare strings are also accepted as input or output schemas. */ export interface TaskSpec { /** @@ -265,13 +312,13 @@ export interface TaskSpec { * response. A bare string is equivalent to a text schema with the same * description. */ - output_schema: JsonSchema | TextSchema | string; + output_schema: JsonSchema | TextSchema | AutoSchema | string; /** * Optional JSON schema or text description of expected input to the task. A bare * string is equivalent to a text schema with the same description. */ - input_schema?: JsonSchema | TextSchema | string | null; + input_schema?: string | JsonSchema | TextSchema | null; } /** @@ -293,7 +340,7 @@ export interface TaskRunCreateParams { /** * Input to the task, either text or a JSON object. */ - input: string | unknown; + input: string | { [key: string]: unknown }; /** * Processor to use for the task. @@ -306,11 +353,20 @@ export interface TaskRunCreateParams { */ metadata?: { [key: string]: string | number | boolean } | null; + /** + * Source policy for web search results. + * + * This policy governs which sources are allowed/disallowed in results. + */ + source_policy?: Shared.SourcePolicy | null; + /** * Specification for a task. * - * For convenience we allow bare strings as input or output schemas, which is - * equivalent to a text schema with the same description. + * Auto output schemas can be specified by setting `output_schema={"type":"auto"}`. + * Not specifying a TaskSpec is the same as setting an auto output schema. + * + * For convenience bare strings are also accepted as input or output schemas. */ task_spec?: TaskSpec | null; } @@ -321,8 +377,11 @@ export interface TaskRunResultParams { export declare namespace TaskRun { export { + type AutoSchema as AutoSchema, type Citation as Citation, + type FieldBasis as FieldBasis, type JsonSchema as JsonSchema, + type RunInput as RunInput, type TaskRun as TaskRun, type TaskRunJsonOutput as TaskRunJsonOutput, type TaskRunResult as TaskRunResult, diff --git a/src/streaming.ts b/src/streaming.ts new file mode 100644 index 0000000..9e6da10 --- /dev/null +++ b/src/streaming.ts @@ -0,0 +1,2 @@ +/** @deprecated Import from ./core/streaming instead */ +export * from './core/streaming'; diff --git a/tests/api-resources/beta/beta.test.ts b/tests/api-resources/beta/beta.test.ts new file mode 100644 index 0000000..f6a03e3 --- /dev/null +++ b/tests/api-resources/beta/beta.test.ts @@ -0,0 +1,21 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Parallel from 'parallel-web'; + +const client = new Parallel({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource beta', () => { + test('search', async () => { + const responsePromise = client.beta.search({}); + 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); + }); +}); diff --git a/tests/api-resources/beta/task-group.test.ts b/tests/api-resources/beta/task-group.test.ts new file mode 100644 index 0000000..612b8bc --- /dev/null +++ b/tests/api-resources/beta/task-group.test.ts @@ -0,0 +1,126 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Parallel from 'parallel-web'; + +const client = new Parallel({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource taskGroup', () => { + test('create', async () => { + const responsePromise = client.beta.taskGroup.create({}); + 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); + }); + + test('retrieve', async () => { + const responsePromise = client.beta.taskGroup.retrieve('taskgroup_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); + }); + + test('addRuns: only required params', async () => { + const responsePromise = client.beta.taskGroup.addRuns('taskgroup_id', { + inputs: [{ input: 'What was the GDP of France in 2023?', processor: 'base' }], + }); + 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); + }); + + test('addRuns: required and optional params', async () => { + const response = await client.beta.taskGroup.addRuns('taskgroup_id', { + inputs: [ + { + input: 'What was the GDP of France in 2023?', + processor: 'base', + enable_events: true, + mcp_servers: [ + { name: 'name', url: 'url', allowed_tools: ['string'], headers: { foo: 'string' }, type: 'url' }, + ], + metadata: { foo: 'string' }, + source_policy: { exclude_domains: ['string'], include_domains: ['string'] }, + task_spec: { + output_schema: { + json_schema: { additionalProperties: 'bar', properties: 'bar', required: 'bar', type: 'bar' }, + type: 'json', + }, + input_schema: 'string', + }, + webhook: { url: 'url', event_types: ['task_run.status'] }, + }, + ], + default_task_spec: { + output_schema: { + json_schema: { additionalProperties: 'bar', properties: 'bar', required: 'bar', type: 'bar' }, + type: 'json', + }, + input_schema: 'string', + }, + betas: ['mcp-server-2025-07-17'], + }); + }); + + // Prism doesn't support text/event-stream responses + test.skip('events', async () => { + const responsePromise = client.beta.taskGroup.events('taskgroup_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 text/event-stream responses + test.skip('events: 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.taskGroup.events( + 'taskgroup_id', + { last_event_id: 'last_event_id', timeout: 0 }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Parallel.NotFoundError); + }); + + // Prism doesn't support text/event-stream responses + test.skip('getRuns', async () => { + const responsePromise = client.beta.taskGroup.getRuns('taskgroup_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 text/event-stream responses + test.skip('getRuns: 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.taskGroup.getRuns( + 'taskgroup_id', + { include_input: true, include_output: true, last_event_id: 'last_event_id', status: 'queued' }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Parallel.NotFoundError); + }); +}); diff --git a/tests/api-resources/beta/task-run.test.ts b/tests/api-resources/beta/task-run.test.ts new file mode 100644 index 0000000..29adaa3 --- /dev/null +++ b/tests/api-resources/beta/task-run.test.ts @@ -0,0 +1,80 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Parallel from 'parallel-web'; + +const client = new Parallel({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource taskRun', () => { + test('create: only required params', async () => { + const responsePromise = client.beta.taskRun.create({ + input: 'What was the GDP of France in 2023?', + processor: 'base', + }); + 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); + }); + + test('create: required and optional params', async () => { + const response = await client.beta.taskRun.create({ + input: 'What was the GDP of France in 2023?', + processor: 'base', + enable_events: true, + mcp_servers: [ + { name: 'name', url: 'url', allowed_tools: ['string'], headers: { foo: 'string' }, type: 'url' }, + ], + metadata: { foo: 'string' }, + source_policy: { exclude_domains: ['string'], include_domains: ['string'] }, + task_spec: { + output_schema: { + json_schema: { additionalProperties: 'bar', properties: 'bar', required: 'bar', type: 'bar' }, + type: 'json', + }, + input_schema: 'string', + }, + webhook: { url: 'url', event_types: ['task_run.status'] }, + betas: ['mcp-server-2025-07-17'], + }); + }); + + // Prism doesn't support text/event-stream responses + test.skip('events', async () => { + const responsePromise = client.beta.taskRun.events('run_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); + }); + + test('result', async () => { + const responsePromise = client.beta.taskRun.result('run_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); + }); + + test('result: 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.taskRun.result( + 'run_id', + { timeout: 0, betas: ['mcp-server-2025-07-17'] }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Parallel.NotFoundError); + }); +}); diff --git a/tests/api-resources/task-run.test.ts b/tests/api-resources/task-run.test.ts index be6e12b..eedd2bb 100644 --- a/tests/api-resources/task-run.test.ts +++ b/tests/api-resources/task-run.test.ts @@ -9,7 +9,10 @@ const client = new Parallel({ describe('resource taskRun', () => { test('create: only required params', async () => { - const responsePromise = client.taskRun.create({ input: 'France (2023)', processor: 'processor' }); + const responsePromise = client.taskRun.create({ + input: 'What was the GDP of France in 2023?', + processor: 'base', + }); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -21,38 +24,16 @@ describe('resource taskRun', () => { test('create: required and optional params', async () => { const response = await client.taskRun.create({ - input: 'France (2023)', - processor: 'processor', + input: 'What was the GDP of France in 2023?', + processor: 'base', metadata: { foo: 'string' }, + source_policy: { exclude_domains: ['string'], include_domains: ['string'] }, task_spec: { output_schema: { - json_schema: { - additionalProperties: false, - properties: { - gdp: { - description: "GDP in USD for the year, formatted like '$3.1 trillion (2023)'", - type: 'string', - }, - }, - required: ['gdp'], - type: 'object', - }, - type: 'json', - }, - input_schema: { - json_schema: { - additionalProperties: false, - properties: { - gdp: { - description: "GDP in USD for the year, formatted like '$3.1 trillion (2023)'", - type: 'string', - }, - }, - required: ['gdp'], - type: 'object', - }, + json_schema: { additionalProperties: 'bar', properties: 'bar', required: 'bar', type: 'bar' }, type: 'json', }, + input_schema: 'string', }, }); }); diff --git a/tests/internal/decoders/line.test.ts b/tests/internal/decoders/line.test.ts new file mode 100644 index 0000000..13e60b9 --- /dev/null +++ b/tests/internal/decoders/line.test.ts @@ -0,0 +1,128 @@ +import { findDoubleNewlineIndex, LineDecoder } from 'parallel-web/internal/decoders/line'; + +function decodeChunks(chunks: string[], { flush }: { flush: boolean } = { flush: false }): string[] { + const decoder = new LineDecoder(); + const lines: string[] = []; + for (const chunk of chunks) { + lines.push(...decoder.decode(chunk)); + } + + if (flush) { + lines.push(...decoder.flush()); + } + + return lines; +} + +describe('line decoder', () => { + test('basic', () => { + // baz is not included because the line hasn't ended yet + expect(decodeChunks(['foo', ' bar\nbaz'])).toEqual(['foo bar']); + }); + + test('basic with \\r', () => { + expect(decodeChunks(['foo', ' bar\r\nbaz'])).toEqual(['foo bar']); + expect(decodeChunks(['foo', ' bar\r\nbaz'], { flush: true })).toEqual(['foo bar', 'baz']); + }); + + test('trailing new lines', () => { + expect(decodeChunks(['foo', ' bar', 'baz\n', 'thing\n'])).toEqual(['foo barbaz', 'thing']); + }); + + test('trailing new lines with \\r', () => { + expect(decodeChunks(['foo', ' bar', 'baz\r\n', 'thing\r\n'])).toEqual(['foo barbaz', 'thing']); + }); + + test('escaped new lines', () => { + expect(decodeChunks(['foo', ' bar\\nbaz\n'])).toEqual(['foo bar\\nbaz']); + }); + + test('escaped new lines with \\r', () => { + expect(decodeChunks(['foo', ' bar\\r\\nbaz\n'])).toEqual(['foo bar\\r\\nbaz']); + }); + + test('\\r & \\n split across multiple chunks', () => { + expect(decodeChunks(['foo\r', '\n', 'bar'], { flush: true })).toEqual(['foo', 'bar']); + }); + + test('single \\r', () => { + expect(decodeChunks(['foo\r', 'bar'], { flush: true })).toEqual(['foo', 'bar']); + }); + + test('double \\r', () => { + expect(decodeChunks(['foo\r', 'bar\r'], { flush: true })).toEqual(['foo', 'bar']); + expect(decodeChunks(['foo\r', '\r', 'bar'], { flush: true })).toEqual(['foo', '', 'bar']); + // implementation detail that we don't yield the single \r line until a new \r or \n is encountered + expect(decodeChunks(['foo\r', '\r', 'bar'], { flush: false })).toEqual(['foo']); + }); + + test('double \\r then \\r\\n', () => { + expect(decodeChunks(['foo\r', '\r', '\r', '\n', 'bar', '\n'])).toEqual(['foo', '', '', 'bar']); + expect(decodeChunks(['foo\n', '\n', '\n', 'bar', '\n'])).toEqual(['foo', '', '', 'bar']); + }); + + test('double newline', () => { + expect(decodeChunks(['foo\n\nbar'], { flush: true })).toEqual(['foo', '', 'bar']); + expect(decodeChunks(['foo', '\n', '\nbar'], { flush: true })).toEqual(['foo', '', 'bar']); + expect(decodeChunks(['foo\n', '\n', 'bar'], { flush: true })).toEqual(['foo', '', 'bar']); + expect(decodeChunks(['foo', '\n', '\n', 'bar'], { flush: true })).toEqual(['foo', '', 'bar']); + }); + + test('multi-byte characters across chunks', () => { + const decoder = new LineDecoder(); + + // bytes taken from the string 'известни' and arbitrarily split + // so that some multi-byte characters span multiple chunks + expect(decoder.decode(new Uint8Array([0xd0]))).toHaveLength(0); + expect(decoder.decode(new Uint8Array([0xb8, 0xd0, 0xb7, 0xd0]))).toHaveLength(0); + expect( + decoder.decode(new Uint8Array([0xb2, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0xd0, 0xbd, 0xd0, 0xb8])), + ).toHaveLength(0); + + const decoded = decoder.decode(new Uint8Array([0xa])); + expect(decoded).toEqual(['известни']); + }); + + test('flushing trailing newlines', () => { + expect(decodeChunks(['foo\n', '\nbar'], { flush: true })).toEqual(['foo', '', 'bar']); + }); + + test('flushing empty buffer', () => { + expect(decodeChunks([], { flush: true })).toEqual([]); + }); +}); + +describe('findDoubleNewlineIndex', () => { + test('finds \\n\\n', () => { + expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\n\nbar'))).toBe(5); + expect(findDoubleNewlineIndex(new TextEncoder().encode('\n\nbar'))).toBe(2); + expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\n\n'))).toBe(5); + expect(findDoubleNewlineIndex(new TextEncoder().encode('\n\n'))).toBe(2); + }); + + test('finds \\r\\r', () => { + expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\rbar'))).toBe(5); + expect(findDoubleNewlineIndex(new TextEncoder().encode('\r\rbar'))).toBe(2); + expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\r'))).toBe(5); + expect(findDoubleNewlineIndex(new TextEncoder().encode('\r\r'))).toBe(2); + }); + + test('finds \\r\\n\\r\\n', () => { + expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\n\r\nbar'))).toBe(7); + expect(findDoubleNewlineIndex(new TextEncoder().encode('\r\n\r\nbar'))).toBe(4); + expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\n\r\n'))).toBe(7); + expect(findDoubleNewlineIndex(new TextEncoder().encode('\r\n\r\n'))).toBe(4); + }); + + test('returns -1 when no double newline found', () => { + expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\nbar'))).toBe(-1); + expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\rbar'))).toBe(-1); + expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\nbar'))).toBe(-1); + expect(findDoubleNewlineIndex(new TextEncoder().encode(''))).toBe(-1); + }); + + test('handles incomplete patterns', () => { + expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\n\r'))).toBe(-1); + expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\n'))).toBe(-1); + }); +}); diff --git a/tests/streaming.test.ts b/tests/streaming.test.ts new file mode 100644 index 0000000..068d3af --- /dev/null +++ b/tests/streaming.test.ts @@ -0,0 +1,219 @@ +import assert from 'assert'; +import { _iterSSEMessages } from 'parallel-web/core/streaming'; +import { ReadableStreamFrom } from 'parallel-web/internal/shims'; + +describe('streaming decoding', () => { + test('basic', async () => { + async function* body(): AsyncGenerator { + yield Buffer.from('event: completion\n'); + yield Buffer.from('data: {"foo":true}\n'); + yield Buffer.from('\n'); + } + + const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ + Symbol.asyncIterator + ](); + + let event = await stream.next(); + assert(event.value); + expect(JSON.parse(event.value.data)).toEqual({ foo: true }); + + event = await stream.next(); + expect(event.done).toBeTruthy(); + }); + + test('data without event', async () => { + async function* body(): AsyncGenerator { + yield Buffer.from('data: {"foo":true}\n'); + yield Buffer.from('\n'); + } + + const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ + Symbol.asyncIterator + ](); + + let event = await stream.next(); + assert(event.value); + expect(event.value.event).toBeNull(); + expect(JSON.parse(event.value.data)).toEqual({ foo: true }); + + event = await stream.next(); + expect(event.done).toBeTruthy(); + }); + + test('event without data', async () => { + async function* body(): AsyncGenerator { + yield Buffer.from('event: foo\n'); + yield Buffer.from('\n'); + } + + const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ + Symbol.asyncIterator + ](); + + let event = await stream.next(); + assert(event.value); + expect(event.value.event).toEqual('foo'); + expect(event.value.data).toEqual(''); + + event = await stream.next(); + expect(event.done).toBeTruthy(); + }); + + test('multiple events', async () => { + async function* body(): AsyncGenerator { + yield Buffer.from('event: foo\n'); + yield Buffer.from('\n'); + yield Buffer.from('event: ping\n'); + yield Buffer.from('\n'); + } + + const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ + Symbol.asyncIterator + ](); + + let event = await stream.next(); + assert(event.value); + expect(event.value.event).toEqual('foo'); + expect(event.value.data).toEqual(''); + + event = await stream.next(); + assert(event.value); + expect(event.value.event).toEqual('ping'); + expect(event.value.data).toEqual(''); + + event = await stream.next(); + expect(event.done).toBeTruthy(); + }); + + test('multiple events with data', async () => { + async function* body(): AsyncGenerator { + yield Buffer.from('event: foo\n'); + yield Buffer.from('data: {"foo":true}\n'); + yield Buffer.from('\n'); + yield Buffer.from('event: ping\n'); + yield Buffer.from('data: {"bar":false}\n'); + yield Buffer.from('\n'); + } + + const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ + Symbol.asyncIterator + ](); + + let event = await stream.next(); + assert(event.value); + expect(event.value.event).toEqual('foo'); + expect(JSON.parse(event.value.data)).toEqual({ foo: true }); + + event = await stream.next(); + assert(event.value); + expect(event.value.event).toEqual('ping'); + expect(JSON.parse(event.value.data)).toEqual({ bar: false }); + + event = await stream.next(); + expect(event.done).toBeTruthy(); + }); + + test('multiple data lines with empty line', async () => { + async function* body(): AsyncGenerator { + yield Buffer.from('event: ping\n'); + yield Buffer.from('data: {\n'); + yield Buffer.from('data: "foo":\n'); + yield Buffer.from('data: \n'); + yield Buffer.from('data:\n'); + yield Buffer.from('data: true}\n'); + yield Buffer.from('\n\n'); + } + + const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ + Symbol.asyncIterator + ](); + + let event = await stream.next(); + assert(event.value); + expect(event.value.event).toEqual('ping'); + expect(JSON.parse(event.value.data)).toEqual({ foo: true }); + expect(event.value.data).toEqual('{\n"foo":\n\n\ntrue}'); + + event = await stream.next(); + expect(event.done).toBeTruthy(); + }); + + test('data json escaped double new line', async () => { + async function* body(): AsyncGenerator { + yield Buffer.from('event: ping\n'); + yield Buffer.from('data: {"foo": "my long\\n\\ncontent"}'); + yield Buffer.from('\n\n'); + } + + const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ + Symbol.asyncIterator + ](); + + let event = await stream.next(); + assert(event.value); + expect(event.value.event).toEqual('ping'); + expect(JSON.parse(event.value.data)).toEqual({ foo: 'my long\n\ncontent' }); + + event = await stream.next(); + expect(event.done).toBeTruthy(); + }); + + test('special new line characters', async () => { + async function* body(): AsyncGenerator { + yield Buffer.from('data: {"content": "culpa "}\n'); + yield Buffer.from('\n'); + yield Buffer.from('data: {"content": "'); + yield Buffer.from([0xe2, 0x80, 0xa8]); + yield Buffer.from('"}\n'); + yield Buffer.from('\n'); + yield Buffer.from('data: {"content": "foo"}\n'); + yield Buffer.from('\n'); + } + + const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ + Symbol.asyncIterator + ](); + + let event = await stream.next(); + assert(event.value); + expect(JSON.parse(event.value.data)).toEqual({ content: 'culpa ' }); + + event = await stream.next(); + assert(event.value); + expect(JSON.parse(event.value.data)).toEqual({ content: Buffer.from([0xe2, 0x80, 0xa8]).toString() }); + + event = await stream.next(); + assert(event.value); + expect(JSON.parse(event.value.data)).toEqual({ content: 'foo' }); + + event = await stream.next(); + expect(event.done).toBeTruthy(); + }); + + test('multi-byte characters across chunks', async () => { + async function* body(): AsyncGenerator { + yield Buffer.from('event: completion\n'); + yield Buffer.from('data: {"content": "'); + // bytes taken from the string 'известни' and arbitrarily split + // so that some multi-byte characters span multiple chunks + yield Buffer.from([0xd0]); + yield Buffer.from([0xb8, 0xd0, 0xb7, 0xd0]); + yield Buffer.from([0xb2, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0xd0, 0xbd, 0xd0, 0xb8]); + yield Buffer.from('"}\n'); + yield Buffer.from('\n'); + } + + const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ + Symbol.asyncIterator + ](); + + let event = await stream.next(); + assert(event.value); + expect(event.value.event).toEqual('completion'); + expect(JSON.parse(event.value.data)).toEqual({ content: 'известни' }); + + event = await stream.next(); + expect(event.done).toBeTruthy(); + }); +}); From f4b9bff96e41c4e31546276be440fb8f721ef595 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 18:29:17 +0000 Subject: [PATCH 3/4] feat(api): update via SDK Studio --- .github/workflows/publish-npm.yml | 32 ++++++++++++++++++++++++++++ .github/workflows/release-doctor.yml | 1 + .stats.yml | 2 +- CONTRIBUTING.md | 14 ++++++++++++ README.md | 5 +---- bin/check-release-environment | 4 ++++ 6 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/publish-npm.yml diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml new file mode 100644 index 0000000..4132771 --- /dev/null +++ b/.github/workflows/publish-npm.yml @@ -0,0 +1,32 @@ +# This workflow is triggered when a GitHub release is created. +# It can also be run manually to re-publish to NPM in case it failed for some reason. +# You can run this workflow by navigating to https://www.github.com/parallel-web/parallel-sdk-typescript/actions/workflows/publish-npm.yml +name: Publish NPM +on: + workflow_dispatch: + + release: + types: [published] + +jobs: + publish: + name: publish + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v3 + with: + node-version: '20' + + - name: Install dependencies + run: | + yarn install + + - name: Publish to NPM + run: | + bash ./bin/publish-npm + env: + NPM_TOKEN: ${{ secrets.PARALLEL_NPM_TOKEN || secrets.NPM_TOKEN }} diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 6ccfd4d..ef694b6 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -18,3 +18,4 @@ jobs: run: | bash ./bin/check-release-environment env: + NPM_TOKEN: ${{ secrets.PARALLEL_NPM_TOKEN || secrets.NPM_TOKEN }} diff --git a/.stats.yml b/.stats.yml index 7c4f552..1835076 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 12 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/parallel-web%2Fparallel-sdk-1aeb1c81a84999f2d27ca9e86b041d74b892926bed126dc9b0f3cff4d7b26963.yml openapi_spec_hash: 6280f6c6fb537f7c9ac5cc33ee2e433d -config_hash: 451edf5a87ae14248aa336ffc08d216f +config_hash: f9aa4d901581aaf70789dd0bc1b84597 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 661f881..51d29f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -91,3 +91,17 @@ To format and fix all lint issues automatically: ```sh $ yarn fix ``` + +## Publishing and releases + +Changes made to this repository via the automated release PR pipeline should publish to npm automatically. If +the changes aren't made through the automated pipeline, you may want to make releases manually. + +### Publish with a GitHub workflow + +You can release to package managers by using [the `Publish NPM` GitHub action](https://www.github.com/parallel-web/parallel-sdk-typescript/actions/workflows/publish-npm.yml). This requires a setup organization or repository secret to be set up. + +### Publish manually + +If you need to manually release a package, you can run the `bin/publish-npm` script with an `NPM_TOKEN` set on +the environment. diff --git a/README.md b/README.md index 20d1935..1f86628 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,9 @@ It is generated with [Stainless](https://www.stainless.com/). ## Installation ```sh -npm install git+ssh://git@github.com:parallel-web/parallel-sdk-typescript.git +npm install parallel-web ``` -> [!NOTE] -> Once this package is [published to npm](https://www.stainless.com/docs/guides/publish), this will become: `npm install parallel-web` - ## Usage The full API of this library can be found in [api.md](api.md). diff --git a/bin/check-release-environment b/bin/check-release-environment index 6b43775..e4b6d58 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -2,6 +2,10 @@ errors=() +if [ -z "${NPM_TOKEN}" ]; then + errors+=("The NPM_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets") +fi + lenErrors=${#errors[@]} if [[ lenErrors -gt 0 ]]; then From 575a0ebf2d66b2abf238f64dd2e16c6bdd4eb451 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 18:30:53 +0000 Subject: [PATCH 4/4] release: 0.1.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 65 +++++++++++++++++++++++++++++++++++ package.json | 2 +- src/version.ts | 2 +- 4 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 67dcd73..466df71 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.1-alpha.0" + ".": "0.1.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8cf29d3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,65 @@ +# Changelog + +## 0.1.0 (2025-09-01) + +Full Changelog: [v0.0.1-alpha.0...v0.1.0](https://github.com/parallel-web/parallel-sdk-typescript/compare/v0.0.1-alpha.0...v0.1.0) + +### Features + +* **api:** update via SDK Studio ([f4b9bff](https://github.com/parallel-web/parallel-sdk-typescript/commit/f4b9bff96e41c4e31546276be440fb8f721ef595)) +* **api:** update via SDK Studio ([7a985fb](https://github.com/parallel-web/parallel-sdk-typescript/commit/7a985fbf420eed89c87940cef40efb650b554c9b)) +* **api:** update via SDK Studio ([bea2209](https://github.com/parallel-web/parallel-sdk-typescript/commit/bea220934b8d11d97e35599dbc68ef3e69bb9523)) +* **api:** update via SDK Studio ([e555533](https://github.com/parallel-web/parallel-sdk-typescript/commit/e5555331ceb4722741248522936df737ab26176e)) +* **api:** update via SDK Studio ([e0922b7](https://github.com/parallel-web/parallel-sdk-typescript/commit/e0922b7ab408dd85495164111d53c3b9a1967da9)) +* **api:** update via SDK Studio ([496a13a](https://github.com/parallel-web/parallel-sdk-typescript/commit/496a13a6922fc072d62c34fa5fb9917824bee881)) +* **api:** update via SDK Studio ([bb43e98](https://github.com/parallel-web/parallel-sdk-typescript/commit/bb43e9850dcb30878dc67d8d5422917d951645b9)) +* **client:** add support for endpoint-specific base URLs ([6ca9e40](https://github.com/parallel-web/parallel-sdk-typescript/commit/6ca9e405e47920c42ab274da43a40f20864fa7c7)) +* **client:** add withOptions helper ([2dc5df5](https://github.com/parallel-web/parallel-sdk-typescript/commit/2dc5df50cfca28e611a360559c694889b9592939)) + + +### Bug Fixes + +* **client:** always overwrite when merging headers ([1b74500](https://github.com/parallel-web/parallel-sdk-typescript/commit/1b745008bbcff7583e8483a3989225fb6eb2719e)) +* **client:** explicitly copy fetch in withOptions ([1e3c4cb](https://github.com/parallel-web/parallel-sdk-typescript/commit/1e3c4cbb08404440b34dc10dc67beb273fa658f3)) +* **client:** get fetchOptions type more reliably ([c90df05](https://github.com/parallel-web/parallel-sdk-typescript/commit/c90df05c6ed462bffb1004861f33ed3f2ff4cdcc)) +* compat with more runtimes ([b197b9f](https://github.com/parallel-web/parallel-sdk-typescript/commit/b197b9ffc02621b1743ba5ff52b59b524aef94b1)) +* publish script — handle NPM errors correctly ([4c38358](https://github.com/parallel-web/parallel-sdk-typescript/commit/4c38358bade9566927ba83f123b06ea70d4c2140)) + + +### Chores + +* add docs to RequestOptions type ([6aae9a9](https://github.com/parallel-web/parallel-sdk-typescript/commit/6aae9a9b10709a8b53886f74e1a768f1b25e66d0)) +* adjust eslint.config.mjs ignore pattern ([1685764](https://github.com/parallel-web/parallel-sdk-typescript/commit/1685764727dd8d217dad3a24257dac5777839b44)) +* avoid type error in certain environments ([dc40584](https://github.com/parallel-web/parallel-sdk-typescript/commit/dc405849f7a23810049ea0409f9bdf4db785d149)) +* change publish docs url ([cd7500b](https://github.com/parallel-web/parallel-sdk-typescript/commit/cd7500b70c2ec7d1dc9534a582619042be13eb53)) +* **ci:** enable for pull requests ([2bd8a50](https://github.com/parallel-web/parallel-sdk-typescript/commit/2bd8a5000c5e442499ab1e7bd4186303e6e0539b)) +* **ci:** only run for pushes and fork pull requests ([07a77b8](https://github.com/parallel-web/parallel-sdk-typescript/commit/07a77b81a447dc1b0f195a44db309913cd6eebcf)) +* **client:** drop support for EOL node versions ([ae8f0d0](https://github.com/parallel-web/parallel-sdk-typescript/commit/ae8f0d0732f35ae967d1285cd3b6255f78822031)) +* **client:** improve path param validation ([c64babf](https://github.com/parallel-web/parallel-sdk-typescript/commit/c64babf6852c1f078b4151f1a9a9e047686dc21f)) +* **client:** refactor imports ([9ed8458](https://github.com/parallel-web/parallel-sdk-typescript/commit/9ed845857b38ff482b624eb171a4767965ce6562)) +* **deps:** bump eslint-plugin-prettier ([326f222](https://github.com/parallel-web/parallel-sdk-typescript/commit/326f222fd6f1f305370681a2aad70ee2776f62e1)) +* **docs:** grammar improvements ([9afb7db](https://github.com/parallel-web/parallel-sdk-typescript/commit/9afb7db5530567fc606fc2084172705b5cf3a627)) +* **docs:** use top-level-await in example snippets ([15c8ab4](https://github.com/parallel-web/parallel-sdk-typescript/commit/15c8ab49b9e9438ccbb3b066def8d2c4ef0f8748)) +* go live ([5e6dbbb](https://github.com/parallel-web/parallel-sdk-typescript/commit/5e6dbbb6f668125403e5261b6ce4bc80861eb627)) +* improve publish-npm script --latest tag logic ([9feb9eb](https://github.com/parallel-web/parallel-sdk-typescript/commit/9feb9eb2fb9ee5d993872a8b8bc4ce30180f8084)) +* **internal:** add pure annotations, make base APIResource abstract ([cf3a8d2](https://github.com/parallel-web/parallel-sdk-typescript/commit/cf3a8d27388f773c166c8283b0e89e9a16ae04e4)) +* **internal:** codegen related update ([7dfd25b](https://github.com/parallel-web/parallel-sdk-typescript/commit/7dfd25bb7ed0a0b7d6a40d74d137b7f2d34c332c)) +* **internal:** fix readablestream types in node 20 ([340f07e](https://github.com/parallel-web/parallel-sdk-typescript/commit/340f07e2c4f1db967f2f00c909b1283c5b0dbad3)) +* **internal:** refactor utils ([95c2945](https://github.com/parallel-web/parallel-sdk-typescript/commit/95c2945cef58d5d7b13c968c70de500148705720)) +* **internal:** share typescript helpers ([fa5b3f1](https://github.com/parallel-web/parallel-sdk-typescript/commit/fa5b3f1e6e9ad059adf73bc8475455247ec71df3)) +* **internal:** update jest config ([b740c53](https://github.com/parallel-web/parallel-sdk-typescript/commit/b740c5312f264b90fd8b8690a5770209486f2e1f)) +* make some internal functions async ([ae148ed](https://github.com/parallel-web/parallel-sdk-typescript/commit/ae148ede844821590bb04b6b5f07e7419660cda4)) +* **package:** remove engines ([8c81289](https://github.com/parallel-web/parallel-sdk-typescript/commit/8c812899a5099a2a5fae23d9863effef9ae66581)) +* **readme:** update badges ([10d3e6a](https://github.com/parallel-web/parallel-sdk-typescript/commit/10d3e6abff37d19ad633430f390f8d899d069bb3)) +* **readme:** use better example snippet for undocumented params ([6b403ec](https://github.com/parallel-web/parallel-sdk-typescript/commit/6b403ecc4ed7dc04bf80d719cd357b3bfd32d47e)) +* **ts:** reorder package.json imports ([4e6e456](https://github.com/parallel-web/parallel-sdk-typescript/commit/4e6e456893e96f2fb0d6cbbf4022193e4207f99e)) + + +### Documentation + +* **readme:** fix typo ([0c499fe](https://github.com/parallel-web/parallel-sdk-typescript/commit/0c499fe10500f3a667941cf8acbdf5d88cb67182)) + + +### Refactors + +* **types:** replace Record with mapped types ([ab87e9c](https://github.com/parallel-web/parallel-sdk-typescript/commit/ab87e9c8c2fdceaf24a3f310647bf0912c1c3dcb)) diff --git a/package.json b/package.json index c05e27e..bf29524 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parallel-web", - "version": "0.0.1-alpha.0", + "version": "0.1.0", "description": "The official TypeScript library for the Parallel API", "author": "Parallel ", "types": "dist/index.d.ts", diff --git a/src/version.ts b/src/version.ts index db692bc..1baa228 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.0.1-alpha.0'; // x-release-please-version +export const VERSION = '0.1.0'; // x-release-please-version