From c6c64aceb8a0a592d2b982463a056e40272ceef0 Mon Sep 17 00:00:00 2001 From: Stainless Bot Date: Wed, 13 Mar 2024 20:30:40 +0000 Subject: [PATCH] feat(assistants): add support for streaming See the reference docs for more information: https://platform.openai.com/docs/api-reference/assistants-streaming We've also improved some of the names for the types in the assistants beta, non exhaustive list: - `CodeToolCall` -> `CodeInterpreterToolCall` - `MessageContentImageFile` -> `ImageFileContentBlock` - `MessageContentText` -> `TextContentBlock` - `ThreadMessage` -> `Message` - `ThreadMessageDeleted` -> `MessageDeleted` --- api.md | 58 +- examples/assistant-stream-raw.ts | 39 + examples/assistant-stream.ts | 48 + examples/assistants.ts | 57 ++ src/index.ts | 1 + src/lib/AbstractAssistantStreamRunner.ts | 340 +++++++ src/lib/AssistantStream.ts | 698 +++++++++++++++ src/resources/beta/assistants/assistants.ts | 844 ++++++++++++++++-- src/resources/beta/assistants/index.ts | 9 + src/resources/beta/beta.ts | 12 + src/resources/beta/index.ts | 12 + src/resources/beta/threads/index.ts | 35 +- src/resources/beta/threads/messages/index.ts | 26 +- .../beta/threads/messages/messages.ts | 426 +++++++-- src/resources/beta/threads/runs/index.ts | 19 +- src/resources/beta/threads/runs/runs.ts | 281 ++++-- src/resources/beta/threads/runs/steps.ts | 259 +++++- src/resources/beta/threads/threads.ts | 207 ++++- src/resources/chat/completions.ts | 2 +- src/resources/completions.ts | 2 + src/resources/shared.ts | 10 + src/streaming.ts | 14 + .../beta/threads/runs/runs.test.ts | 2 + .../beta/threads/threads.test.ts | 1 + tests/streaming/assistants/assistant.test.ts | 32 + 25 files changed, 3155 insertions(+), 279 deletions(-) create mode 100644 examples/assistant-stream-raw.ts create mode 100644 examples/assistant-stream.ts create mode 100644 examples/assistants.ts create mode 100644 src/lib/AbstractAssistantStreamRunner.ts create mode 100644 src/lib/AssistantStream.ts create mode 100644 tests/streaming/assistants/assistant.test.ts diff --git a/api.md b/api.md index ff3180cba..504a103c7 100644 --- a/api.md +++ b/api.md @@ -2,6 +2,7 @@ Types: +- ErrorObject - FunctionDefinition - FunctionParameters @@ -177,6 +178,15 @@ Types: - Assistant - AssistantDeleted +- AssistantStreamEvent +- AssistantTool +- CodeInterpreterTool +- FunctionTool +- MessageStreamEvent +- RetrievalTool +- RunStepStreamEvent +- RunStreamEvent +- ThreadStreamEvent Methods: @@ -214,6 +224,7 @@ Methods: - client.beta.threads.update(threadId, { ...params }) -> Thread - client.beta.threads.del(threadId) -> ThreadDeleted - client.beta.threads.createAndRun({ ...params }) -> Run +- client.beta.threads.createAndRunStream(body, options?) -> AssistantStream ### Runs @@ -231,16 +242,29 @@ Methods: - client.beta.threads.runs.list(threadId, { ...params }) -> RunsPage - client.beta.threads.runs.cancel(threadId, runId) -> Run - client.beta.threads.runs.submitToolOutputs(threadId, runId, { ...params }) -> Run +- client.beta.threads.runs.createAndStream(threadId, body, options?) -> AssistantStream +- client.beta.threads.runs.submitToolOutputsStream(threadId, runId, body, options?) -> AssistantStream #### Steps Types: -- CodeToolCall +- CodeInterpreterLogs +- CodeInterpreterOutputImage +- CodeInterpreterToolCall +- CodeInterpreterToolCallDelta - FunctionToolCall +- FunctionToolCallDelta - MessageCreationStepDetails - RetrievalToolCall +- RetrievalToolCallDelta - RunStep +- RunStepDelta +- RunStepDeltaEvent +- RunStepDeltaMessageDelta +- ToolCall +- ToolCallDelta +- ToolCallDeltaObject - ToolCallsStepDetails Methods: @@ -252,17 +276,33 @@ Methods: Types: -- MessageContentImageFile -- MessageContentText -- ThreadMessage -- ThreadMessageDeleted +- Annotation +- AnnotationDelta +- FileCitationAnnotation +- FileCitationDeltaAnnotation +- FilePathAnnotation +- FilePathDeltaAnnotation +- ImageFile +- ImageFileContentBlock +- ImageFileDelta +- ImageFileDeltaBlock +- Message +- MessageContent +- MessageContentDelta +- MessageDeleted +- MessageDelta +- MessageDeltaEvent +- Text +- TextContentBlock +- TextDelta +- TextDeltaBlock Methods: -- client.beta.threads.messages.create(threadId, { ...params }) -> ThreadMessage -- client.beta.threads.messages.retrieve(threadId, messageId) -> ThreadMessage -- client.beta.threads.messages.update(threadId, messageId, { ...params }) -> ThreadMessage -- client.beta.threads.messages.list(threadId, { ...params }) -> ThreadMessagesPage +- client.beta.threads.messages.create(threadId, { ...params }) -> Message +- client.beta.threads.messages.retrieve(threadId, messageId) -> Message +- client.beta.threads.messages.update(threadId, messageId, { ...params }) -> Message +- client.beta.threads.messages.list(threadId, { ...params }) -> MessagesPage #### Files diff --git a/examples/assistant-stream-raw.ts b/examples/assistant-stream-raw.ts new file mode 100644 index 000000000..a882d219a --- /dev/null +++ b/examples/assistant-stream-raw.ts @@ -0,0 +1,39 @@ +import OpenAI from 'openai'; + +const openai = new OpenAI(); + +async function main() { + const assistant = await openai.beta.assistants.create({ + model: 'gpt-4-1106-preview', + name: 'Math Tutor', + instructions: 'You are a personal math tutor. Write and run code to answer math questions.', + }); + + const thread = await openai.beta.threads.create({ + messages: [ + { + role: 'user', + content: '"I need to solve the equation `3x + 11 = 14`. Can you help me?"', + }, + ], + }); + + const stream = await openai.beta.threads.runs.create(thread.id, { + assistant_id: assistant.id, + additional_instructions: 'Please address the user as Jane Doe. The user has a premium account.', + stream: true, + }); + + for await (const event of stream) { + if (event.event === 'thread.message.delta') { + const chunk = event.data.delta.content?.[0]; + if (chunk && 'text' in chunk) { + process.stdout.write(chunk.text.value); + } + } + } + + console.log(); +} + +main(); diff --git a/examples/assistant-stream.ts b/examples/assistant-stream.ts new file mode 100644 index 000000000..36c4ed152 --- /dev/null +++ b/examples/assistant-stream.ts @@ -0,0 +1,48 @@ +#!/usr/bin/env -S npm run tsn -T + +import OpenAI from 'openai'; + +/** + * Example of streaming a response from an assistant + */ + +const openai = new OpenAI(); + +async function main() { + const assistant = await openai.beta.assistants.create({ + model: 'gpt-4-1106-preview', + name: 'Math Tutor', + instructions: 'You are a personal math tutor. Write and run code to answer math questions.', + }); + + let assistantId = assistant.id; + console.log('Created Assistant with Id: ' + assistantId); + + const thread = await openai.beta.threads.create({ + messages: [ + { + role: 'user', + content: '"I need to solve the equation `3x + 11 = 14`. Can you help me?"', + }, + ], + }); + + let threadId = thread.id; + console.log('Created thread with Id: ' + threadId); + + const run = openai.beta.threads.runs + .createAndStream(threadId, { + assistant_id: assistantId, + }) + //Subscribe to streaming events and log them + .on('event', (event) => console.log(event)) + .on('textDelta', (delta, snapshot) => console.log(snapshot)) + .on('messageDelta', (delta, snapshot) => console.log(snapshot)) + .on('run', (run) => console.log(run)) + .on('messageDelta', (delta, snapshot) => console.log(snapshot)) + .on('connect', () => console.log()); + const result = await run.finalRun(); + console.log('Run Result' + result); +} + +main(); diff --git a/examples/assistants.ts b/examples/assistants.ts new file mode 100644 index 000000000..bbc2f80ce --- /dev/null +++ b/examples/assistants.ts @@ -0,0 +1,57 @@ +#!/usr/bin/env -S npm run tsn -T + +import OpenAI from 'openai'; +import { sleep } from 'openai/core'; + +/** + * Example of polling for a complete response from an assistant + */ + +const openai = new OpenAI(); + +async function main() { + const assistant = await openai.beta.assistants.create({ + model: 'gpt-4-1106-preview', + name: 'Math Tutor', + instructions: 'You are a personal math tutor. Write and run code to answer math questions.', + // tools = [], + }); + + let assistantId = assistant.id; + console.log('Created Assistant with Id: ' + assistantId); + + const thread = await openai.beta.threads.create({ + messages: [ + { + role: 'user', + content: '"I need to solve the equation `3x + 11 = 14`. Can you help me?"', + }, + ], + }); + + let threadId = thread.id; + console.log('Created thread with Id: ' + threadId); + + const run = await openai.beta.threads.runs.create(thread.id, { + assistant_id: assistantId, + additional_instructions: 'Please address the user as Jane Doe. The user has a premium account.', + }); + + console.log('Created run with Id: ' + run.id); + + while (true) { + const result = await openai.beta.threads.runs.retrieve(thread.id, run.id); + if (result.status == 'completed') { + const messages = await openai.beta.threads.messages.list(thread.id); + for (const message of messages.getPaginatedItems()) { + console.log(message); + } + break; + } else { + console.log('Waiting for completion. Current status: ' + result.status); + await sleep(5000); + } + } +} + +main(); diff --git a/src/index.ts b/src/index.ts index 80bf95b0d..7b3033fa9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -285,6 +285,7 @@ export namespace OpenAI { export import Beta = API.Beta; + export import ErrorObject = API.ErrorObject; export import FunctionDefinition = API.FunctionDefinition; export import FunctionParameters = API.FunctionParameters; } diff --git a/src/lib/AbstractAssistantStreamRunner.ts b/src/lib/AbstractAssistantStreamRunner.ts new file mode 100644 index 000000000..b600f0df3 --- /dev/null +++ b/src/lib/AbstractAssistantStreamRunner.ts @@ -0,0 +1,340 @@ +import * as Core from 'openai/core'; +import { APIUserAbortError, OpenAIError } from 'openai/error'; +import { Run, RunSubmitToolOutputsParamsBase } from 'openai/resources/beta/threads/runs/runs'; +import { RunCreateParamsBase, Runs } from 'openai/resources/beta/threads/runs/runs'; +import { ThreadCreateAndRunParamsBase, Threads } from 'openai/resources/beta/threads/threads'; + +export abstract class AbstractAssistantStreamRunner< + Events extends CustomEvents = AbstractAssistantRunnerEvents, +> { + controller: AbortController = new AbortController(); + + #connectedPromise: Promise; + #resolveConnectedPromise: () => void = () => {}; + #rejectConnectedPromise: (error: OpenAIError) => void = () => {}; + + #endPromise: Promise; + #resolveEndPromise: () => void = () => {}; + #rejectEndPromise: (error: OpenAIError) => void = () => {}; + + #listeners: { [Event in keyof Events]?: ListenersForEvent } = {}; + + #ended = false; + #errored = false; + #aborted = false; + #catchingPromiseCreated = false; + + constructor() { + this.#connectedPromise = new Promise((resolve, reject) => { + this.#resolveConnectedPromise = resolve; + this.#rejectConnectedPromise = reject; + }); + + this.#endPromise = new Promise((resolve, reject) => { + this.#resolveEndPromise = resolve; + this.#rejectEndPromise = reject; + }); + + // Don't let these promises cause unhandled rejection errors. + // we will manually cause an unhandled rejection error later + // if the user hasn't registered any error listener or called + // any promise-returning method. + this.#connectedPromise.catch(() => {}); + this.#endPromise.catch(() => {}); + } + + protected _run(executor: () => Promise) { + // Unfortunately if we call `executor()` immediately we get runtime errors about + // references to `this` before the `super()` constructor call returns. + setTimeout(() => { + executor().then(() => { + // this._emitFinal(); + this._emit('end'); + }, this.#handleError); + }, 0); + } + + protected _addRun(run: Run): Run { + return run; + } + + protected _connected() { + if (this.ended) return; + this.#resolveConnectedPromise(); + this._emit('connect'); + } + + get ended(): boolean { + return this.#ended; + } + + get errored(): boolean { + return this.#errored; + } + + get aborted(): boolean { + return this.#aborted; + } + + abort() { + this.controller.abort(); + } + + /** + * Adds the listener function to the end of the listeners array for the event. + * No checks are made to see if the listener has already been added. Multiple calls passing + * the same combination of event and listener will result in the listener being added, and + * called, multiple times. + * @returns this ChatCompletionStream, so that calls can be chained + */ + on(event: Event, listener: ListenerForEvent): this { + const listeners: ListenersForEvent = + this.#listeners[event] || (this.#listeners[event] = []); + listeners.push({ listener }); + return this; + } + + /** + * Removes the specified listener from the listener array for the event. + * off() will remove, at most, one instance of a listener from the listener array. If any single + * listener has been added multiple times to the listener array for the specified event, then + * off() must be called multiple times to remove each instance. + * @returns this ChatCompletionStream, so that calls can be chained + */ + off(event: Event, listener: ListenerForEvent): this { + const listeners = this.#listeners[event]; + if (!listeners) return this; + const index = listeners.findIndex((l) => l.listener === listener); + if (index >= 0) listeners.splice(index, 1); + return this; + } + + /** + * Adds a one-time listener function for the event. The next time the event is triggered, + * this listener is removed and then invoked. + * @returns this ChatCompletionStream, so that calls can be chained + */ + once(event: Event, listener: ListenerForEvent): this { + const listeners: ListenersForEvent = + this.#listeners[event] || (this.#listeners[event] = []); + listeners.push({ listener, once: true }); + return this; + } + + /** + * This is similar to `.once()`, but returns a Promise that resolves the next time + * the event is triggered, instead of calling a listener callback. + * @returns a Promise that resolves the next time given event is triggered, + * or rejects if an error is emitted. (If you request the 'error' event, + * returns a promise that resolves with the error). + * + * Example: + * + * const message = await stream.emitted('message') // rejects if the stream errors + */ + emitted( + event: Event, + ): Promise< + EventParameters extends [infer Param] ? Param + : EventParameters extends [] ? void + : EventParameters + > { + return new Promise((resolve, reject) => { + this.#catchingPromiseCreated = true; + if (event !== 'error') this.once('error', reject); + this.once(event, resolve as any); + }); + } + + async done(): Promise { + this.#catchingPromiseCreated = true; + await this.#endPromise; + } + + #handleError = (error: unknown) => { + this.#errored = true; + if (error instanceof Error && error.name === 'AbortError') { + error = new APIUserAbortError(); + } + if (error instanceof APIUserAbortError) { + this.#aborted = true; + return this._emit('abort', error); + } + if (error instanceof OpenAIError) { + return this._emit('error', error); + } + if (error instanceof Error) { + const openAIError: OpenAIError = new OpenAIError(error.message); + // @ts-ignore + openAIError.cause = error; + return this._emit('error', openAIError); + } + return this._emit('error', new OpenAIError(String(error))); + }; + + protected _emit(event: Event, ...args: EventParameters) { + // make sure we don't emit any events after end + if (this.#ended) { + return; + } + + if (event === 'end') { + this.#ended = true; + this.#resolveEndPromise(); + } + + const listeners: ListenersForEvent | undefined = this.#listeners[event]; + if (listeners) { + this.#listeners[event] = listeners.filter((l) => !l.once) as any; + listeners.forEach(({ listener }: any) => listener(...args)); + } + + if (event === 'abort') { + const error = args[0] as APIUserAbortError; + if (!this.#catchingPromiseCreated && !listeners?.length) { + Promise.reject(error); + } + this.#rejectConnectedPromise(error); + this.#rejectEndPromise(error); + this._emit('end'); + return; + } + + if (event === 'error') { + // NOTE: _emit('error', error) should only be called from #handleError(). + + const error = args[0] as OpenAIError; + if (!this.#catchingPromiseCreated && !listeners?.length) { + // Trigger an unhandled rejection if the user hasn't registered any error handlers. + // If you are seeing stack traces here, make sure to handle errors via either: + // - runner.on('error', () => ...) + // - await runner.done() + // - await runner.finalChatCompletion() + // - etc. + Promise.reject(error); + } + this.#rejectConnectedPromise(error); + this.#rejectEndPromise(error); + this._emit('end'); + } + } + + protected async _threadAssistantStream( + body: ThreadCreateAndRunParamsBase, + thread: Threads, + options?: Core.RequestOptions, + ): Promise { + return await this._createThreadAssistantStream(thread, body, options); + } + + protected async _runAssistantStream( + threadId: string, + runs: Runs, + params: RunCreateParamsBase, + options?: Core.RequestOptions, + ): Promise { + return await this._createAssistantStream(runs, threadId, params, options); + } + + protected async _runToolAssistantStream( + threadId: string, + runId: string, + runs: Runs, + params: RunSubmitToolOutputsParamsBase, + options?: Core.RequestOptions, + ): Promise { + return await this._createToolAssistantStream(runs, threadId, runId, params, options); + } + + protected async _createThreadAssistantStream( + thread: Threads, + body: ThreadCreateAndRunParamsBase, + options?: Core.RequestOptions, + ): Promise { + const signal = options?.signal; + if (signal) { + if (signal.aborted) this.controller.abort(); + signal.addEventListener('abort', () => this.controller.abort()); + } + // this.#validateParams(params); + + const runResult = await thread.createAndRun( + { ...body, stream: false }, + { ...options, signal: this.controller.signal }, + ); + this._connected(); + return this._addRun(runResult as Run); + } + + protected async _createToolAssistantStream( + run: Runs, + threadId: string, + runId: string, + params: RunSubmitToolOutputsParamsBase, + options?: Core.RequestOptions, + ): Promise { + const signal = options?.signal; + if (signal) { + if (signal.aborted) this.controller.abort(); + signal.addEventListener('abort', () => this.controller.abort()); + } + + const runResult = await run.submitToolOutputs( + threadId, + runId, + { ...params, stream: false }, + { ...options, signal: this.controller.signal }, + ); + this._connected(); + return this._addRun(runResult as Run); + } + + protected async _createAssistantStream( + run: Runs, + threadId: string, + params: RunCreateParamsBase, + options?: Core.RequestOptions, + ): Promise { + const signal = options?.signal; + if (signal) { + if (signal.aborted) this.controller.abort(); + signal.addEventListener('abort', () => this.controller.abort()); + } + // this.#validateParams(params); + + const runResult = await run.create( + threadId, + { ...params, stream: false }, + { ...options, signal: this.controller.signal }, + ); + this._connected(); + return this._addRun(runResult as Run); + } +} + +type CustomEvents = { + [k in Event]: k extends keyof AbstractAssistantRunnerEvents ? AbstractAssistantRunnerEvents[k] + : (...args: any[]) => void; +}; + +type ListenerForEvent, Event extends keyof Events> = Event extends ( + keyof AbstractAssistantRunnerEvents +) ? + AbstractAssistantRunnerEvents[Event] +: Events[Event]; + +type ListenersForEvent, Event extends keyof Events> = Array<{ + listener: ListenerForEvent; + once?: boolean; +}>; +type EventParameters, Event extends keyof Events> = Parameters< + ListenerForEvent +>; + +export interface AbstractAssistantRunnerEvents { + connect: () => void; + run: (run: Run) => void; + error: (error: OpenAIError) => void; + abort: (error: APIUserAbortError) => void; + end: () => void; +} diff --git a/src/lib/AssistantStream.ts b/src/lib/AssistantStream.ts new file mode 100644 index 000000000..d70cb7358 --- /dev/null +++ b/src/lib/AssistantStream.ts @@ -0,0 +1,698 @@ +import { + TextContentBlock, + ImageFileContentBlock, + Message, + MessageContentDelta, + Text, + ImageFile, + TextDelta, + Messages, +} from 'openai/resources/beta/threads/messages/messages'; +import * as Core from 'openai/core'; +import { RequestOptions } from 'openai/core'; +import { + Run, + RunCreateParamsBase, + RunCreateParamsStreaming, + Runs, + RunSubmitToolOutputsParamsBase, + RunSubmitToolOutputsParamsStreaming, +} from 'openai/resources/beta/threads/runs/runs'; +import { + AbstractAssistantRunnerEvents, + AbstractAssistantStreamRunner, +} from './AbstractAssistantStreamRunner'; +import { type ReadableStream } from 'openai/_shims/index'; +import { Stream } from 'openai/streaming'; +import { APIUserAbortError, OpenAIError } from 'openai/error'; +import { + AssistantStreamEvent, + MessageStreamEvent, + RunStepStreamEvent, + RunStreamEvent, +} from 'openai/resources/beta/assistants/assistants'; +import { RunStep, RunStepDelta, ToolCall, ToolCallDelta } from 'openai/resources/beta/threads/runs/steps'; +import { ThreadCreateAndRunParamsBase, Threads } from 'openai/resources/beta/threads/threads'; +import MessageDelta = Messages.MessageDelta; + +export interface AssistantStreamEvents extends AbstractAssistantRunnerEvents { + //New event structure + messageCreated: (message: Message) => void; + messageDelta: (message: MessageDelta, snapshot: Message) => void; + messageDone: (message: Message) => void; + + runStepCreated: (runStep: RunStep) => void; + runStepDelta: (delta: RunStepDelta, snapshot: Runs.RunStep) => void; + runStepDone: (runStep: Runs.RunStep, snapshot: Runs.RunStep) => void; + + toolCallCreated: (toolCall: ToolCall) => void; + toolCallDelta: (delta: ToolCallDelta, snapshot: ToolCall) => void; + toolCallDone: (toolCall: ToolCall) => void; + + textCreated: (content: Text) => void; + textDelta: (delta: TextDelta, snapshot: Text) => void; + textDone: (content: Text, snapshot: Message) => void; + + //No created or delta as this is not streamed + imageFileDone: (content: ImageFile, snapshot: Message) => void; + + end: () => void; + + event: (event: AssistantStreamEvent) => void; +} + +export type ThreadCreateAndRunParamsBaseStream = Omit & { + stream?: true; +}; + +export type RunCreateParamsBaseStream = Omit & { + stream?: true; +}; + +export type RunSubmitToolOutputsParamsStream = Omit & { + stream?: true; +}; + +export class AssistantStream + extends AbstractAssistantStreamRunner + implements AsyncIterable +{ + //Track all events in a single list for reference + #events: AssistantStreamEvent[] = []; + + //Used to accumulate deltas + //We are accumulating many types so the value here is not strict + #runStepSnapshots: { [id: string]: Runs.RunStep } = {}; + #messageSnapshots: { [id: string]: Message } = {}; + #messageSnapshot: Message | undefined; + #finalRun: Run | undefined; + #currentContentIndex: number | undefined; + #currentContent: TextContentBlock | ImageFileContentBlock | undefined; + #currentToolCallIndex: number | undefined; + #currentToolCall: ToolCall | undefined; + + //For current snapshot methods + #currentEvent: AssistantStreamEvent | undefined; + #currentRunSnapshot: Run | undefined; + #currentRunStepSnapshot: Runs.RunStep | undefined; + + [Symbol.asyncIterator](): AsyncIterator { + const pushQueue: AssistantStreamEvent[] = []; + const readQueue: { + resolve: (chunk: AssistantStreamEvent | undefined) => void; + reject: (err: unknown) => void; + }[] = []; + let done = false; + + //Catch all for passing along all events + this.on('event', (event) => { + const reader = readQueue.shift(); + if (reader) { + reader.resolve(event); + } else { + pushQueue.push(event); + } + }); + + this.on('end', () => { + done = true; + for (const reader of readQueue) { + reader.resolve(undefined); + } + readQueue.length = 0; + }); + + this.on('abort', (err) => { + done = true; + for (const reader of readQueue) { + reader.reject(err); + } + readQueue.length = 0; + }); + + this.on('error', (err) => { + done = true; + for (const reader of readQueue) { + reader.reject(err); + } + readQueue.length = 0; + }); + + return { + next: async (): Promise> => { + if (!pushQueue.length) { + if (done) { + return { value: undefined, done: true }; + } + return new Promise((resolve, reject) => + readQueue.push({ resolve, reject }), + ).then((chunk) => (chunk ? { value: chunk, done: false } : { value: undefined, done: true })); + } + const chunk = pushQueue.shift()!; + return { value: chunk, done: false }; + }, + return: async () => { + this.abort(); + return { value: undefined, done: true }; + }, + }; + } + + toReadableStream(): ReadableStream { + const stream = new Stream(this[Symbol.asyncIterator].bind(this), this.controller); + return stream.toReadableStream(); + } + + static createToolAssistantStream( + threadId: string, + runId: string, + runs: Runs, + body: RunSubmitToolOutputsParamsStream, + options: RequestOptions | undefined, + ) { + const runner = new AssistantStream(); + runner._run(() => + runner._runToolAssistantStream(threadId, runId, runs, body, { + ...options, + headers: { ...options?.headers, 'X-Stainless-Helper-Method': 'stream' }, + }), + ); + return runner; + } + + protected override async _createToolAssistantStream( + run: Runs, + threadId: string, + runId: string, + params: RunSubmitToolOutputsParamsStream, + options?: Core.RequestOptions, + ): Promise { + const signal = options?.signal; + if (signal) { + if (signal.aborted) this.controller.abort(); + signal.addEventListener('abort', () => this.controller.abort()); + } + + const body: RunSubmitToolOutputsParamsStreaming = { ...params, stream: true }; + const stream = await run.submitToolOutputs(threadId, runId, body, { + ...options, + signal: this.controller.signal, + }); + + this._connected(); + + for await (const event of stream) { + this.#addEvent(event); + } + if (stream.controller.signal?.aborted) { + throw new APIUserAbortError(); + } + + return this._addRun(this.#endRequest()); + } + + static createThreadAssistantStream( + body: ThreadCreateAndRunParamsBaseStream, + thread: Threads, + options?: RequestOptions, + ) { + const runner = new AssistantStream(); + runner._run(() => + runner._threadAssistantStream(body, thread, { + ...options, + headers: { ...options?.headers, 'X-Stainless-Helper-Method': 'stream' }, + }), + ); + return runner; + } + + static createAssistantStream( + threadId: string, + runs: Runs, + params: RunCreateParamsBaseStream, + options?: RequestOptions, + ) { + const runner = new AssistantStream(); + runner._run(() => + runner._runAssistantStream(threadId, runs, params, { + ...options, + headers: { ...options?.headers, 'X-Stainless-Helper-Method': 'stream' }, + }), + ); + return runner; + } + + currentEvent(): AssistantStreamEvent | undefined { + return this.#currentEvent; + } + + currentRun(): Run | undefined { + return this.#currentRunSnapshot; + } + + currentMessageSnapshot(): Message | undefined { + return this.#messageSnapshot; + } + + currentRunStepSnapshot(): Runs.RunStep | undefined { + return this.#currentRunStepSnapshot; + } + + async finalRunSteps(): Promise { + await this.done(); + + return Object.values(this.#runStepSnapshots); + } + + async finalMessages(): Promise { + await this.done(); + + return Object.values(this.#messageSnapshots); + } + + async finalRun(): Promise { + await this.done(); + if (!this.#finalRun) throw Error('Final run was not received.'); + + return this.#finalRun; + } + + protected override async _createThreadAssistantStream( + thread: Threads, + params: ThreadCreateAndRunParamsBase, + options?: Core.RequestOptions, + ): Promise { + const signal = options?.signal; + if (signal) { + if (signal.aborted) this.controller.abort(); + signal.addEventListener('abort', () => this.controller.abort()); + } + + const body: RunCreateParamsStreaming = { ...params, stream: true }; + const stream = await thread.createAndRun(body, { ...options, signal: this.controller.signal }); + + this._connected(); + + for await (const event of stream) { + this.#addEvent(event); + } + if (stream.controller.signal?.aborted) { + throw new APIUserAbortError(); + } + + return this._addRun(this.#endRequest()); + } + + protected override async _createAssistantStream( + run: Runs, + threadId: string, + params: RunCreateParamsBase, + options?: Core.RequestOptions, + ): Promise { + const signal = options?.signal; + if (signal) { + if (signal.aborted) this.controller.abort(); + signal.addEventListener('abort', () => this.controller.abort()); + } + + const body: RunCreateParamsStreaming = { ...params, stream: true }; + const stream = await run.create(threadId, body, { ...options, signal: this.controller.signal }); + + this._connected(); + + for await (const event of stream) { + this.#addEvent(event); + } + if (stream.controller.signal?.aborted) { + throw new APIUserAbortError(); + } + + return this._addRun(this.#endRequest()); + } + + #addEvent(event: AssistantStreamEvent) { + if (this.ended) return; + + this.#currentEvent = event; + + this.#handleEvent(event); + + switch (event.event) { + case 'thread.created': + //No action on this event. + break; + + case 'thread.run.created': + case 'thread.run.queued': + case 'thread.run.in_progress': + case 'thread.run.requires_action': + case 'thread.run.completed': + case 'thread.run.failed': + case 'thread.run.cancelling': + case 'thread.run.cancelled': + case 'thread.run.expired': + this.#handleRun(event); + break; + + case 'thread.run.step.created': + case 'thread.run.step.in_progress': + case 'thread.run.step.delta': + case 'thread.run.step.completed': + case 'thread.run.step.failed': + case 'thread.run.step.cancelled': + case 'thread.run.step.expired': + this.#handleRunStep(event); + break; + + case 'thread.message.created': + case 'thread.message.in_progress': + case 'thread.message.delta': + case 'thread.message.completed': + case 'thread.message.incomplete': + this.#handleMessage(event); + break; + + case 'error': + //This is included for completeness, but errors are processed in the SSE event processing so this should not occur + throw new Error( + 'Encountered an error event in event processing - errors should be processed earlier', + ); + } + } + + #endRequest(): Run { + if (this.ended) { + throw new OpenAIError(`stream has ended, this shouldn't happen`); + } + + if (!this.#finalRun) throw Error('Final run has been been received'); + + return this.#finalRun; + } + + #handleMessage(event: MessageStreamEvent) { + const [accumulatedMessage, newContent] = this.#accumulateMessage(event, this.#messageSnapshot); + this.#messageSnapshot = accumulatedMessage; + this.#messageSnapshots[accumulatedMessage.id] = accumulatedMessage; + + for (const content of newContent) { + const snapshotContent = accumulatedMessage.content[content.index]; + if (snapshotContent?.type == 'text') { + this._emit('textCreated', snapshotContent.text); + } + } + + switch (event.event) { + case 'thread.message.created': + this._emit('messageCreated', event.data); + break; + + case 'thread.message.in_progress': + break; + + case 'thread.message.delta': + this._emit('messageDelta', event.data.delta, accumulatedMessage); + + if (event.data.delta.content) { + for (const content of event.data.delta.content) { + //If it is text delta, emit a text delta event + if (content.type == 'text' && content.text) { + let textDelta = content.text; + let snapshot = accumulatedMessage.content[content.index]; + if (snapshot && snapshot.type == 'text') { + this._emit('textDelta', textDelta, snapshot.text); + } else { + throw Error('The snapshot associated with this text delta is not text or missing'); + } + } + + if (content.index != this.#currentContentIndex) { + //See if we have in progress content + if (this.#currentContent) { + switch (this.#currentContent.type) { + case 'text': + this._emit('textDone', this.#currentContent.text, this.#messageSnapshot); + break; + case 'image_file': + this._emit('imageFileDone', this.#currentContent.image_file, this.#messageSnapshot); + break; + } + } + + this.#currentContentIndex = content.index; + } + + this.#currentContent = accumulatedMessage.content[content.index]; + } + } + + break; + + case 'thread.message.completed': + case 'thread.message.incomplete': + //We emit the latest content we were working on on completion (including incomplete) + if (this.#currentContentIndex !== undefined) { + const currentContent = event.data.content[this.#currentContentIndex]; + if (currentContent) { + switch (currentContent.type) { + case 'image_file': + this._emit('imageFileDone', currentContent.image_file, this.#messageSnapshot); + break; + case 'text': + this._emit('textDone', currentContent.text, this.#messageSnapshot); + break; + } + } + } + + if (this.#messageSnapshot) { + this._emit('messageDone', event.data); + } + + this.#messageSnapshot = undefined; + } + } + + #handleRunStep(event: RunStepStreamEvent) { + const accumulatedRunStep = this.#accumulateRunStep(event); + this.#currentRunStepSnapshot = accumulatedRunStep; + + switch (event.event) { + case 'thread.run.step.created': + this._emit('runStepCreated', event.data); + break; + case 'thread.run.step.delta': + const delta = event.data.delta; + if ( + delta.step_details && + delta.step_details.type == 'tool_calls' && + delta.step_details.tool_calls && + accumulatedRunStep.step_details.type == 'tool_calls' + ) { + for (const toolCall of delta.step_details.tool_calls) { + if (toolCall.index == this.#currentToolCallIndex) { + this._emit( + 'toolCallDelta', + toolCall, + accumulatedRunStep.step_details.tool_calls[toolCall.index] as ToolCall, + ); + } else { + if (this.#currentToolCall) { + this._emit('toolCallDone', this.#currentToolCall); + } + + this.#currentToolCallIndex = toolCall.index; + this.#currentToolCall = accumulatedRunStep.step_details.tool_calls[toolCall.index]; + if (this.#currentToolCall) this._emit('toolCallCreated', this.#currentToolCall); + } + } + } + + this._emit('runStepDelta', event.data.delta, accumulatedRunStep); + break; + case 'thread.run.step.completed': + case 'thread.run.step.failed': + case 'thread.run.step.cancelled': + case 'thread.run.step.expired': + this.#currentRunStepSnapshot = undefined; + const details = event.data.step_details; + if (details.type == 'tool_calls') { + if (this.#currentToolCall) { + this._emit('toolCallDone', this.#currentToolCall as ToolCall); + this.#currentToolCall = undefined; + } + } + this._emit('runStepDone', event.data, accumulatedRunStep); + break; + case 'thread.run.step.in_progress': + break; + } + } + + #handleEvent(event: AssistantStreamEvent) { + this.#events.push(event); + this._emit('event', event); + } + + #accumulateRunStep(event: RunStepStreamEvent): Runs.RunStep { + switch (event.event) { + case 'thread.run.step.created': + this.#runStepSnapshots[event.data.id] = event.data; + return event.data; + + case 'thread.run.step.delta': + let snapshot = this.#runStepSnapshots[event.data.id] as Runs.RunStep; + if (!snapshot) { + throw Error('Received a RunStepDelta before creation of a snapshot'); + } + + let data = event.data; + + if (data.delta) { + const accumulated = AssistantStream.accumulateDelta(snapshot, data.delta) as Runs.RunStep; + this.#runStepSnapshots[event.data.id] = accumulated; + } + + return this.#runStepSnapshots[event.data.id] as Runs.RunStep; + + case 'thread.run.step.completed': + case 'thread.run.step.failed': + case 'thread.run.step.cancelled': + case 'thread.run.step.expired': + case 'thread.run.step.in_progress': + this.#runStepSnapshots[event.data.id] = event.data; + break; + } + + if (this.#runStepSnapshots[event.data.id]) return this.#runStepSnapshots[event.data.id] as Runs.RunStep; + throw new Error('No snapshot available'); + } + + #accumulateMessage( + event: AssistantStreamEvent, + snapshot: Message | undefined, + ): [Message, MessageContentDelta[]] { + let newContent: MessageContentDelta[] = []; + + switch (event.event) { + case 'thread.message.created': + //On creation the snapshot is just the initial message + return [event.data, newContent]; + + case 'thread.message.delta': + if (!snapshot) { + throw Error( + 'Received a delta with no existing snapshot (there should be one from message creation)', + ); + } + + let data = event.data; + + //If this delta does not have content, nothing to process + if (data.delta.content) { + for (const contentElement of data.delta.content) { + if (contentElement.index in snapshot.content) { + let currentContent = snapshot.content[contentElement.index]; + snapshot.content[contentElement.index] = this.#accumulateContent( + contentElement, + currentContent, + ); + } else { + snapshot.content[contentElement.index] = contentElement as + | TextContentBlock + | ImageFileContentBlock; + //This is a new element + newContent.push(contentElement); + } + } + } + + return [snapshot, newContent]; + + case 'thread.message.in_progress': + case 'thread.message.completed': + case 'thread.message.incomplete': + //No changes on other thread events + if (snapshot) { + return [snapshot, newContent]; + } else { + throw Error('Received thread message event with no existing snapshot'); + } + } + throw Error('Tried to accumulate a non-message event'); + } + + #accumulateContent( + contentElement: MessageContentDelta, + currentContent: TextContentBlock | ImageFileContentBlock | undefined, + ): TextContentBlock | ImageFileContentBlock { + return AssistantStream.accumulateDelta(currentContent as unknown as Record, contentElement) as + | TextContentBlock + | ImageFileContentBlock; + } + + static accumulateDelta(acc: Record, delta: Record): Record { + for (const [key, deltaValue] of Object.entries(delta)) { + if (!acc.hasOwnProperty(key)) { + acc[key] = deltaValue; + continue; + } + + let accValue = acc[key]; + if (accValue === null || accValue === undefined) { + acc[key] = deltaValue; + continue; + } + + // We don't accumulate these special properties + if (key === 'index' || key === 'type') { + acc[key] = deltaValue; + continue; + } + + // Type-specific accumulation logic + if (typeof accValue === 'string' && typeof deltaValue === 'string') { + accValue += deltaValue; + } else if (typeof accValue === 'number' && typeof deltaValue === 'number') { + accValue += deltaValue; + } else if (Core.isObj(accValue) && Core.isObj(deltaValue)) { + accValue = this.accumulateDelta(accValue as Record, deltaValue as Record); + } else if (Array.isArray(accValue) && Array.isArray(deltaValue)) { + if (accValue.every((x) => typeof x === 'string' || typeof x === 'number')) { + accValue.push(...deltaValue); // Use spread syntax for efficient addition + continue; + } + } else { + throw Error(`Unhandled record type: ${key}, deltaValue: ${deltaValue}, accValue: ${accValue}`); + } + acc[key] = accValue; + } + + return acc; + } + + #handleRun(event: RunStreamEvent) { + this.#currentRunSnapshot = event.data; + switch (event.event) { + case 'thread.run.created': + break; + case 'thread.run.queued': + break; + case 'thread.run.in_progress': + break; + case 'thread.run.requires_action': + case 'thread.run.cancelled': + case 'thread.run.failed': + case 'thread.run.completed': + case 'thread.run.expired': + this.#finalRun = event.data; + if (this.#currentToolCall) { + this._emit('toolCallDone', this.#currentToolCall); + this.#currentToolCall = undefined; + } + break; + case 'thread.run.cancelling': + break; + } + } +} diff --git a/src/resources/beta/assistants/assistants.ts b/src/resources/beta/assistants/assistants.ts index 08abb2c91..b4e92fd92 100644 --- a/src/resources/beta/assistants/assistants.ts +++ b/src/resources/beta/assistants/assistants.ts @@ -6,6 +6,10 @@ import { isRequestOptions } from 'openai/core'; import * as AssistantsAPI from 'openai/resources/beta/assistants/assistants'; import * as Shared from 'openai/resources/shared'; import * as FilesAPI from 'openai/resources/beta/assistants/files'; +import * as ThreadsAPI from 'openai/resources/beta/threads/threads'; +import * as MessagesAPI from 'openai/resources/beta/threads/messages/messages'; +import * as RunsAPI from 'openai/resources/beta/threads/runs/runs'; +import * as StepsAPI from 'openai/resources/beta/threads/runs/steps'; import { CursorPage, type CursorPageParams } from 'openai/pagination'; export class Assistants extends APIResource { @@ -145,40 +149,777 @@ export interface Assistant { * A list of tool enabled on the assistant. There can be a maximum of 128 tools per * assistant. Tools can be of types `code_interpreter`, `retrieval`, or `function`. */ - tools: Array; + tools: Array; } -export namespace Assistant { - export interface CodeInterpreter { +export interface AssistantDeleted { + id: string; + + deleted: boolean; + + object: 'assistant.deleted'; +} + +/** + * Represents an event emitted when streaming a Run. + * + * Each event in a server-sent events stream has an `event` and `data` property: + * + * ``` + * event: thread.created + * data: {"id": "thread_123", "object": "thread", ...} + * ``` + * + * We emit events whenever a new object is created, transitions to a new state, or + * is being streamed in parts (deltas). For example, we emit `thread.run.created` + * when a new run is created, `thread.run.completed` when a run completes, and so + * on. When an Assistant chooses to create a message during a run, we emit a + * `thread.message.created event`, a `thread.message.in_progress` event, many + * `thread.message.delta` events, and finally a `thread.message.completed` event. + * + * We may add additional events over time, so we recommend handling unknown events + * gracefully in your code. See the + * [Assistants API quickstart](https://platform.openai.com/docs/assistants/overview) + * to learn how to integrate the Assistants API with streaming. + */ +export type AssistantStreamEvent = + | AssistantStreamEvent.ThreadCreated + | AssistantStreamEvent.ThreadRunCreated + | AssistantStreamEvent.ThreadRunQueued + | AssistantStreamEvent.ThreadRunInProgress + | AssistantStreamEvent.ThreadRunRequiresAction + | AssistantStreamEvent.ThreadRunCompleted + | AssistantStreamEvent.ThreadRunFailed + | AssistantStreamEvent.ThreadRunCancelling + | AssistantStreamEvent.ThreadRunCancelled + | AssistantStreamEvent.ThreadRunExpired + | AssistantStreamEvent.ThreadRunStepCreated + | AssistantStreamEvent.ThreadRunStepInProgress + | AssistantStreamEvent.ThreadRunStepDelta + | AssistantStreamEvent.ThreadRunStepCompleted + | AssistantStreamEvent.ThreadRunStepFailed + | AssistantStreamEvent.ThreadRunStepCancelled + | AssistantStreamEvent.ThreadRunStepExpired + | AssistantStreamEvent.ThreadMessageCreated + | AssistantStreamEvent.ThreadMessageInProgress + | AssistantStreamEvent.ThreadMessageDelta + | AssistantStreamEvent.ThreadMessageCompleted + | AssistantStreamEvent.ThreadMessageIncomplete + | AssistantStreamEvent.ErrorEvent; + +export namespace AssistantStreamEvent { + /** + * Occurs when a new + * [thread](https://platform.openai.com/docs/api-reference/threads/object) is + * created. + */ + export interface ThreadCreated { + /** + * Represents a thread that contains + * [messages](https://platform.openai.com/docs/api-reference/messages). + */ + data: ThreadsAPI.Thread; + + event: 'thread.created'; + } + + /** + * Occurs when a new + * [run](https://platform.openai.com/docs/api-reference/runs/object) is created. + */ + export interface ThreadRunCreated { + /** + * Represents an execution run on a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: RunsAPI.Run; + + event: 'thread.run.created'; + } + + /** + * Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) + * moves to a `queued` status. + */ + export interface ThreadRunQueued { + /** + * Represents an execution run on a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: RunsAPI.Run; + + event: 'thread.run.queued'; + } + + /** + * Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) + * moves to an `in_progress` status. + */ + export interface ThreadRunInProgress { + /** + * Represents an execution run on a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: RunsAPI.Run; + + event: 'thread.run.in_progress'; + } + + /** + * Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) + * moves to a `requires_action` status. + */ + export interface ThreadRunRequiresAction { + /** + * Represents an execution run on a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: RunsAPI.Run; + + event: 'thread.run.requires_action'; + } + + /** + * Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) + * is completed. + */ + export interface ThreadRunCompleted { + /** + * Represents an execution run on a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: RunsAPI.Run; + + event: 'thread.run.completed'; + } + + /** + * Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) + * fails. + */ + export interface ThreadRunFailed { + /** + * Represents an execution run on a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: RunsAPI.Run; + + event: 'thread.run.failed'; + } + + /** + * Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) + * moves to a `cancelling` status. + */ + export interface ThreadRunCancelling { + /** + * Represents an execution run on a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: RunsAPI.Run; + + event: 'thread.run.cancelling'; + } + + /** + * Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) + * is cancelled. + */ + export interface ThreadRunCancelled { /** - * The type of tool being defined: `code_interpreter` + * Represents an execution run on a + * [thread](https://platform.openai.com/docs/api-reference/threads). */ - type: 'code_interpreter'; + data: RunsAPI.Run; + + event: 'thread.run.cancelled'; } - export interface Retrieval { + /** + * Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) + * expires. + */ + export interface ThreadRunExpired { /** - * The type of tool being defined: `retrieval` + * Represents an execution run on a + * [thread](https://platform.openai.com/docs/api-reference/threads). */ - type: 'retrieval'; + data: RunsAPI.Run; + + event: 'thread.run.expired'; } - export interface Function { - function: Shared.FunctionDefinition; + /** + * Occurs when a + * [run step](https://platform.openai.com/docs/api-reference/runs/step-object) is + * created. + */ + export interface ThreadRunStepCreated { + /** + * Represents a step in execution of a run. + */ + data: StepsAPI.RunStep; + + event: 'thread.run.step.created'; + } + /** + * Occurs when a + * [run step](https://platform.openai.com/docs/api-reference/runs/step-object) + * moves to an `in_progress` state. + */ + export interface ThreadRunStepInProgress { /** - * The type of tool being defined: `function` + * Represents a step in execution of a run. */ - type: 'function'; + data: StepsAPI.RunStep; + + event: 'thread.run.step.in_progress'; + } + + /** + * Occurs when parts of a + * [run step](https://platform.openai.com/docs/api-reference/runs/step-object) are + * being streamed. + */ + export interface ThreadRunStepDelta { + /** + * Represents a run step delta i.e. any changed fields on a run step during + * streaming. + */ + data: StepsAPI.RunStepDeltaEvent; + + event: 'thread.run.step.delta'; + } + + /** + * Occurs when a + * [run step](https://platform.openai.com/docs/api-reference/runs/step-object) is + * completed. + */ + export interface ThreadRunStepCompleted { + /** + * Represents a step in execution of a run. + */ + data: StepsAPI.RunStep; + + event: 'thread.run.step.completed'; + } + + /** + * Occurs when a + * [run step](https://platform.openai.com/docs/api-reference/runs/step-object) + * fails. + */ + export interface ThreadRunStepFailed { + /** + * Represents a step in execution of a run. + */ + data: StepsAPI.RunStep; + + event: 'thread.run.step.failed'; + } + + /** + * Occurs when a + * [run step](https://platform.openai.com/docs/api-reference/runs/step-object) is + * cancelled. + */ + export interface ThreadRunStepCancelled { + /** + * Represents a step in execution of a run. + */ + data: StepsAPI.RunStep; + + event: 'thread.run.step.cancelled'; + } + + /** + * Occurs when a + * [run step](https://platform.openai.com/docs/api-reference/runs/step-object) + * expires. + */ + export interface ThreadRunStepExpired { + /** + * Represents a step in execution of a run. + */ + data: StepsAPI.RunStep; + + event: 'thread.run.step.expired'; + } + + /** + * Occurs when a + * [message](https://platform.openai.com/docs/api-reference/messages/object) is + * created. + */ + export interface ThreadMessageCreated { + /** + * Represents a message within a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: MessagesAPI.Message; + + event: 'thread.message.created'; + } + + /** + * Occurs when a + * [message](https://platform.openai.com/docs/api-reference/messages/object) moves + * to an `in_progress` state. + */ + export interface ThreadMessageInProgress { + /** + * Represents a message within a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: MessagesAPI.Message; + + event: 'thread.message.in_progress'; + } + + /** + * Occurs when parts of a + * [Message](https://platform.openai.com/docs/api-reference/messages/object) are + * being streamed. + */ + export interface ThreadMessageDelta { + /** + * Represents a message delta i.e. any changed fields on a message during + * streaming. + */ + data: MessagesAPI.MessageDeltaEvent; + + event: 'thread.message.delta'; + } + + /** + * Occurs when a + * [message](https://platform.openai.com/docs/api-reference/messages/object) is + * completed. + */ + export interface ThreadMessageCompleted { + /** + * Represents a message within a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: MessagesAPI.Message; + + event: 'thread.message.completed'; + } + + /** + * Occurs when a + * [message](https://platform.openai.com/docs/api-reference/messages/object) ends + * before it is completed. + */ + export interface ThreadMessageIncomplete { + /** + * Represents a message within a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: MessagesAPI.Message; + + event: 'thread.message.incomplete'; + } + + /** + * Occurs when an + * [error](https://platform.openai.com/docs/guides/error-codes/api-errors) occurs. + * This can happen due to an internal server error or a timeout. + */ + export interface ErrorEvent { + data: Shared.ErrorObject; + + event: 'error'; } } -export interface AssistantDeleted { - id: string; +export type AssistantTool = CodeInterpreterTool | RetrievalTool | FunctionTool; - deleted: boolean; +export interface CodeInterpreterTool { + /** + * The type of tool being defined: `code_interpreter` + */ + type: 'code_interpreter'; +} - object: 'assistant.deleted'; +export interface FunctionTool { + function: Shared.FunctionDefinition; + + /** + * The type of tool being defined: `function` + */ + type: 'function'; +} + +/** + * Occurs when a + * [message](https://platform.openai.com/docs/api-reference/messages/object) is + * created. + */ +export type MessageStreamEvent = + | MessageStreamEvent.ThreadMessageCreated + | MessageStreamEvent.ThreadMessageInProgress + | MessageStreamEvent.ThreadMessageDelta + | MessageStreamEvent.ThreadMessageCompleted + | MessageStreamEvent.ThreadMessageIncomplete; + +export namespace MessageStreamEvent { + /** + * Occurs when a + * [message](https://platform.openai.com/docs/api-reference/messages/object) is + * created. + */ + export interface ThreadMessageCreated { + /** + * Represents a message within a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: MessagesAPI.Message; + + event: 'thread.message.created'; + } + + /** + * Occurs when a + * [message](https://platform.openai.com/docs/api-reference/messages/object) moves + * to an `in_progress` state. + */ + export interface ThreadMessageInProgress { + /** + * Represents a message within a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: MessagesAPI.Message; + + event: 'thread.message.in_progress'; + } + + /** + * Occurs when parts of a + * [Message](https://platform.openai.com/docs/api-reference/messages/object) are + * being streamed. + */ + export interface ThreadMessageDelta { + /** + * Represents a message delta i.e. any changed fields on a message during + * streaming. + */ + data: MessagesAPI.MessageDeltaEvent; + + event: 'thread.message.delta'; + } + + /** + * Occurs when a + * [message](https://platform.openai.com/docs/api-reference/messages/object) is + * completed. + */ + export interface ThreadMessageCompleted { + /** + * Represents a message within a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: MessagesAPI.Message; + + event: 'thread.message.completed'; + } + + /** + * Occurs when a + * [message](https://platform.openai.com/docs/api-reference/messages/object) ends + * before it is completed. + */ + export interface ThreadMessageIncomplete { + /** + * Represents a message within a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: MessagesAPI.Message; + + event: 'thread.message.incomplete'; + } +} + +export interface RetrievalTool { + /** + * The type of tool being defined: `retrieval` + */ + type: 'retrieval'; +} + +/** + * Occurs when a + * [run step](https://platform.openai.com/docs/api-reference/runs/step-object) is + * created. + */ +export type RunStepStreamEvent = + | RunStepStreamEvent.ThreadRunStepCreated + | RunStepStreamEvent.ThreadRunStepInProgress + | RunStepStreamEvent.ThreadRunStepDelta + | RunStepStreamEvent.ThreadRunStepCompleted + | RunStepStreamEvent.ThreadRunStepFailed + | RunStepStreamEvent.ThreadRunStepCancelled + | RunStepStreamEvent.ThreadRunStepExpired; + +export namespace RunStepStreamEvent { + /** + * Occurs when a + * [run step](https://platform.openai.com/docs/api-reference/runs/step-object) is + * created. + */ + export interface ThreadRunStepCreated { + /** + * Represents a step in execution of a run. + */ + data: StepsAPI.RunStep; + + event: 'thread.run.step.created'; + } + + /** + * Occurs when a + * [run step](https://platform.openai.com/docs/api-reference/runs/step-object) + * moves to an `in_progress` state. + */ + export interface ThreadRunStepInProgress { + /** + * Represents a step in execution of a run. + */ + data: StepsAPI.RunStep; + + event: 'thread.run.step.in_progress'; + } + + /** + * Occurs when parts of a + * [run step](https://platform.openai.com/docs/api-reference/runs/step-object) are + * being streamed. + */ + export interface ThreadRunStepDelta { + /** + * Represents a run step delta i.e. any changed fields on a run step during + * streaming. + */ + data: StepsAPI.RunStepDeltaEvent; + + event: 'thread.run.step.delta'; + } + + /** + * Occurs when a + * [run step](https://platform.openai.com/docs/api-reference/runs/step-object) is + * completed. + */ + export interface ThreadRunStepCompleted { + /** + * Represents a step in execution of a run. + */ + data: StepsAPI.RunStep; + + event: 'thread.run.step.completed'; + } + + /** + * Occurs when a + * [run step](https://platform.openai.com/docs/api-reference/runs/step-object) + * fails. + */ + export interface ThreadRunStepFailed { + /** + * Represents a step in execution of a run. + */ + data: StepsAPI.RunStep; + + event: 'thread.run.step.failed'; + } + + /** + * Occurs when a + * [run step](https://platform.openai.com/docs/api-reference/runs/step-object) is + * cancelled. + */ + export interface ThreadRunStepCancelled { + /** + * Represents a step in execution of a run. + */ + data: StepsAPI.RunStep; + + event: 'thread.run.step.cancelled'; + } + + /** + * Occurs when a + * [run step](https://platform.openai.com/docs/api-reference/runs/step-object) + * expires. + */ + export interface ThreadRunStepExpired { + /** + * Represents a step in execution of a run. + */ + data: StepsAPI.RunStep; + + event: 'thread.run.step.expired'; + } +} + +/** + * Occurs when a new + * [run](https://platform.openai.com/docs/api-reference/runs/object) is created. + */ +export type RunStreamEvent = + | RunStreamEvent.ThreadRunCreated + | RunStreamEvent.ThreadRunQueued + | RunStreamEvent.ThreadRunInProgress + | RunStreamEvent.ThreadRunRequiresAction + | RunStreamEvent.ThreadRunCompleted + | RunStreamEvent.ThreadRunFailed + | RunStreamEvent.ThreadRunCancelling + | RunStreamEvent.ThreadRunCancelled + | RunStreamEvent.ThreadRunExpired; + +export namespace RunStreamEvent { + /** + * Occurs when a new + * [run](https://platform.openai.com/docs/api-reference/runs/object) is created. + */ + export interface ThreadRunCreated { + /** + * Represents an execution run on a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: RunsAPI.Run; + + event: 'thread.run.created'; + } + + /** + * Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) + * moves to a `queued` status. + */ + export interface ThreadRunQueued { + /** + * Represents an execution run on a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: RunsAPI.Run; + + event: 'thread.run.queued'; + } + + /** + * Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) + * moves to an `in_progress` status. + */ + export interface ThreadRunInProgress { + /** + * Represents an execution run on a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: RunsAPI.Run; + + event: 'thread.run.in_progress'; + } + + /** + * Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) + * moves to a `requires_action` status. + */ + export interface ThreadRunRequiresAction { + /** + * Represents an execution run on a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: RunsAPI.Run; + + event: 'thread.run.requires_action'; + } + + /** + * Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) + * is completed. + */ + export interface ThreadRunCompleted { + /** + * Represents an execution run on a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: RunsAPI.Run; + + event: 'thread.run.completed'; + } + + /** + * Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) + * fails. + */ + export interface ThreadRunFailed { + /** + * Represents an execution run on a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: RunsAPI.Run; + + event: 'thread.run.failed'; + } + + /** + * Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) + * moves to a `cancelling` status. + */ + export interface ThreadRunCancelling { + /** + * Represents an execution run on a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: RunsAPI.Run; + + event: 'thread.run.cancelling'; + } + + /** + * Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) + * is cancelled. + */ + export interface ThreadRunCancelled { + /** + * Represents an execution run on a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: RunsAPI.Run; + + event: 'thread.run.cancelled'; + } + + /** + * Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) + * expires. + */ + export interface ThreadRunExpired { + /** + * Represents an execution run on a + * [thread](https://platform.openai.com/docs/api-reference/threads). + */ + data: RunsAPI.Run; + + event: 'thread.run.expired'; + } +} + +/** + * Occurs when a new + * [thread](https://platform.openai.com/docs/api-reference/threads/object) is + * created. + */ +export interface ThreadStreamEvent { + /** + * Represents a thread that contains + * [messages](https://platform.openai.com/docs/api-reference/messages). + */ + data: ThreadsAPI.Thread; + + event: 'thread.created'; } export interface AssistantCreateParams { @@ -226,36 +967,7 @@ export interface AssistantCreateParams { * A list of tool enabled on the assistant. There can be a maximum of 128 tools per * assistant. Tools can be of types `code_interpreter`, `retrieval`, or `function`. */ - tools?: Array< - | AssistantCreateParams.AssistantToolsCode - | AssistantCreateParams.AssistantToolsRetrieval - | AssistantCreateParams.AssistantToolsFunction - >; -} - -export namespace AssistantCreateParams { - export interface AssistantToolsCode { - /** - * The type of tool being defined: `code_interpreter` - */ - type: 'code_interpreter'; - } - - export interface AssistantToolsRetrieval { - /** - * The type of tool being defined: `retrieval` - */ - type: 'retrieval'; - } - - export interface AssistantToolsFunction { - function: Shared.FunctionDefinition; - - /** - * The type of tool being defined: `function` - */ - type: 'function'; - } + tools?: Array; } export interface AssistantUpdateParams { @@ -305,36 +1017,7 @@ export interface AssistantUpdateParams { * A list of tool enabled on the assistant. There can be a maximum of 128 tools per * assistant. Tools can be of types `code_interpreter`, `retrieval`, or `function`. */ - tools?: Array< - | AssistantUpdateParams.AssistantToolsCode - | AssistantUpdateParams.AssistantToolsRetrieval - | AssistantUpdateParams.AssistantToolsFunction - >; -} - -export namespace AssistantUpdateParams { - export interface AssistantToolsCode { - /** - * The type of tool being defined: `code_interpreter` - */ - type: 'code_interpreter'; - } - - export interface AssistantToolsRetrieval { - /** - * The type of tool being defined: `retrieval` - */ - type: 'retrieval'; - } - - export interface AssistantToolsFunction { - function: Shared.FunctionDefinition; - - /** - * The type of tool being defined: `function` - */ - type: 'function'; - } + tools?: Array; } export interface AssistantListParams extends CursorPageParams { @@ -356,6 +1039,15 @@ export interface AssistantListParams extends CursorPageParams { export namespace Assistants { export import Assistant = AssistantsAPI.Assistant; export import AssistantDeleted = AssistantsAPI.AssistantDeleted; + export import AssistantStreamEvent = AssistantsAPI.AssistantStreamEvent; + export import AssistantTool = AssistantsAPI.AssistantTool; + export import CodeInterpreterTool = AssistantsAPI.CodeInterpreterTool; + export import FunctionTool = AssistantsAPI.FunctionTool; + export import MessageStreamEvent = AssistantsAPI.MessageStreamEvent; + export import RetrievalTool = AssistantsAPI.RetrievalTool; + export import RunStepStreamEvent = AssistantsAPI.RunStepStreamEvent; + export import RunStreamEvent = AssistantsAPI.RunStreamEvent; + export import ThreadStreamEvent = AssistantsAPI.ThreadStreamEvent; export import AssistantsPage = AssistantsAPI.AssistantsPage; export import AssistantCreateParams = AssistantsAPI.AssistantCreateParams; export import AssistantUpdateParams = AssistantsAPI.AssistantUpdateParams; diff --git a/src/resources/beta/assistants/index.ts b/src/resources/beta/assistants/index.ts index 5236bc8de..0ae8c9c67 100644 --- a/src/resources/beta/assistants/index.ts +++ b/src/resources/beta/assistants/index.ts @@ -3,6 +3,15 @@ export { Assistant, AssistantDeleted, + AssistantStreamEvent, + AssistantTool, + CodeInterpreterTool, + FunctionTool, + MessageStreamEvent, + RetrievalTool, + RunStepStreamEvent, + RunStreamEvent, + ThreadStreamEvent, AssistantCreateParams, AssistantUpdateParams, AssistantListParams, diff --git a/src/resources/beta/beta.ts b/src/resources/beta/beta.ts index 5fd99990d..74056ed1d 100644 --- a/src/resources/beta/beta.ts +++ b/src/resources/beta/beta.ts @@ -16,6 +16,15 @@ export namespace Beta { export import Assistants = AssistantsAPI.Assistants; export import Assistant = AssistantsAPI.Assistant; export import AssistantDeleted = AssistantsAPI.AssistantDeleted; + export import AssistantStreamEvent = AssistantsAPI.AssistantStreamEvent; + export import AssistantTool = AssistantsAPI.AssistantTool; + export import CodeInterpreterTool = AssistantsAPI.CodeInterpreterTool; + export import FunctionTool = AssistantsAPI.FunctionTool; + export import MessageStreamEvent = AssistantsAPI.MessageStreamEvent; + export import RetrievalTool = AssistantsAPI.RetrievalTool; + export import RunStepStreamEvent = AssistantsAPI.RunStepStreamEvent; + export import RunStreamEvent = AssistantsAPI.RunStreamEvent; + export import ThreadStreamEvent = AssistantsAPI.ThreadStreamEvent; export import AssistantsPage = AssistantsAPI.AssistantsPage; export import AssistantCreateParams = AssistantsAPI.AssistantCreateParams; export import AssistantUpdateParams = AssistantsAPI.AssistantUpdateParams; @@ -26,4 +35,7 @@ export namespace Beta { export import ThreadCreateParams = ThreadsAPI.ThreadCreateParams; export import ThreadUpdateParams = ThreadsAPI.ThreadUpdateParams; export import ThreadCreateAndRunParams = ThreadsAPI.ThreadCreateAndRunParams; + export import ThreadCreateAndRunParamsNonStreaming = ThreadsAPI.ThreadCreateAndRunParamsNonStreaming; + export import ThreadCreateAndRunParamsStreaming = ThreadsAPI.ThreadCreateAndRunParamsStreaming; + export import ThreadCreateAndRunStreamParams = ThreadsAPI.ThreadCreateAndRunStreamParams; } diff --git a/src/resources/beta/index.ts b/src/resources/beta/index.ts index 4ed7e84b1..d8770c29a 100644 --- a/src/resources/beta/index.ts +++ b/src/resources/beta/index.ts @@ -3,6 +3,15 @@ export { Assistant, AssistantDeleted, + AssistantStreamEvent, + AssistantTool, + CodeInterpreterTool, + FunctionTool, + MessageStreamEvent, + RetrievalTool, + RunStepStreamEvent, + RunStreamEvent, + ThreadStreamEvent, AssistantCreateParams, AssistantUpdateParams, AssistantListParams, @@ -17,5 +26,8 @@ export { ThreadCreateParams, ThreadUpdateParams, ThreadCreateAndRunParams, + ThreadCreateAndRunParamsNonStreaming, + ThreadCreateAndRunParamsStreaming, + ThreadCreateAndRunStreamParams, Threads, } from './threads/index'; diff --git a/src/resources/beta/threads/index.ts b/src/resources/beta/threads/index.ts index 54a02dd03..3585be846 100644 --- a/src/resources/beta/threads/index.ts +++ b/src/resources/beta/threads/index.ts @@ -1,14 +1,30 @@ // File generated from our OpenAPI spec by Stainless. export { - MessageContentImageFile, - MessageContentText, - ThreadMessage, - ThreadMessageDeleted, + Annotation, + AnnotationDelta, + FileCitationAnnotation, + FileCitationDeltaAnnotation, + FilePathAnnotation, + FilePathDeltaAnnotation, + ImageFile, + ImageFileContentBlock, + ImageFileDelta, + ImageFileDeltaBlock, + Message, + MessageContent, + MessageContentDelta, + MessageDeleted, + MessageDelta, + MessageDeltaEvent, + Text, + TextContentBlock, + TextDelta, + TextDeltaBlock, MessageCreateParams, MessageUpdateParams, MessageListParams, - ThreadMessagesPage, + MessagesPage, Messages, } from './messages/index'; export { @@ -16,9 +32,15 @@ export { Run, RunStatus, RunCreateParams, + RunCreateParamsNonStreaming, + RunCreateParamsStreaming, RunUpdateParams, RunListParams, + RunCreateAndStreamParams, RunSubmitToolOutputsParams, + RunSubmitToolOutputsParamsNonStreaming, + RunSubmitToolOutputsParamsStreaming, + RunSubmitToolOutputsStreamParams, RunsPage, Runs, } from './runs/index'; @@ -28,5 +50,8 @@ export { ThreadCreateParams, ThreadUpdateParams, ThreadCreateAndRunParams, + ThreadCreateAndRunParamsNonStreaming, + ThreadCreateAndRunParamsStreaming, + ThreadCreateAndRunStreamParams, Threads, } from './threads'; diff --git a/src/resources/beta/threads/messages/index.ts b/src/resources/beta/threads/messages/index.ts index cde22c2a9..f68edbbd4 100644 --- a/src/resources/beta/threads/messages/index.ts +++ b/src/resources/beta/threads/messages/index.ts @@ -1,14 +1,30 @@ // File generated from our OpenAPI spec by Stainless. export { - MessageContentImageFile, - MessageContentText, - ThreadMessage, - ThreadMessageDeleted, + Annotation, + AnnotationDelta, + FileCitationAnnotation, + FileCitationDeltaAnnotation, + FilePathAnnotation, + FilePathDeltaAnnotation, + ImageFile, + ImageFileContentBlock, + ImageFileDelta, + ImageFileDeltaBlock, + Message, + MessageContent, + MessageContentDelta, + MessageDeleted, + MessageDelta, + MessageDeltaEvent, + Text, + TextContentBlock, + TextDelta, + TextDeltaBlock, MessageCreateParams, MessageUpdateParams, MessageListParams, - ThreadMessagesPage, + MessagesPage, Messages, } from './messages'; export { MessageFile, FileListParams, MessageFilesPage, Files } from './files'; diff --git a/src/resources/beta/threads/messages/messages.ts b/src/resources/beta/threads/messages/messages.ts index 40b436829..b38a4bbf0 100644 --- a/src/resources/beta/threads/messages/messages.ts +++ b/src/resources/beta/threads/messages/messages.ts @@ -17,7 +17,7 @@ export class Messages extends APIResource { threadId: string, body: MessageCreateParams, options?: Core.RequestOptions, - ): Core.APIPromise { + ): Core.APIPromise { return this._client.post(`/threads/${threadId}/messages`, { body, ...options, @@ -28,11 +28,7 @@ export class Messages extends APIResource { /** * Retrieve a message. */ - retrieve( - threadId: string, - messageId: string, - options?: Core.RequestOptions, - ): Core.APIPromise { + retrieve(threadId: string, messageId: string, options?: Core.RequestOptions): Core.APIPromise { return this._client.get(`/threads/${threadId}/messages/${messageId}`, { ...options, headers: { 'OpenAI-Beta': 'assistants=v1', ...options?.headers }, @@ -47,7 +43,7 @@ export class Messages extends APIResource { messageId: string, body: MessageUpdateParams, options?: Core.RequestOptions, - ): Core.APIPromise { + ): Core.APIPromise { return this._client.post(`/threads/${threadId}/messages/${messageId}`, { body, ...options, @@ -62,17 +58,17 @@ export class Messages extends APIResource { threadId: string, query?: MessageListParams, options?: Core.RequestOptions, - ): Core.PagePromise; - list(threadId: string, options?: Core.RequestOptions): Core.PagePromise; + ): Core.PagePromise; + list(threadId: string, options?: Core.RequestOptions): Core.PagePromise; list( threadId: string, query: MessageListParams | Core.RequestOptions = {}, options?: Core.RequestOptions, - ): Core.PagePromise { + ): Core.PagePromise { if (isRequestOptions(query)) { return this.list(threadId, {}, query); } - return this._client.getAPIList(`/threads/${threadId}/messages`, ThreadMessagesPage, { + return this._client.getAPIList(`/threads/${threadId}/messages`, MessagesPage, { query, ...options, headers: { 'OpenAI-Beta': 'assistants=v1', ...options?.headers }, @@ -80,129 +76,220 @@ export class Messages extends APIResource { } } -export class ThreadMessagesPage extends CursorPage {} +export class MessagesPage extends CursorPage {} /** - * References an image [File](https://platform.openai.com/docs/api-reference/files) - * in the content of a message. + * A citation within the message that points to a specific quote from a specific + * File associated with the assistant or the message. Generated when the assistant + * uses the "retrieval" tool to search files. + */ +export type Annotation = FileCitationAnnotation | FilePathAnnotation; + +/** + * A citation within the message that points to a specific quote from a specific + * File associated with the assistant or the message. Generated when the assistant + * uses the "retrieval" tool to search files. + */ +export type AnnotationDelta = FileCitationDeltaAnnotation | FilePathDeltaAnnotation; + +/** + * A citation within the message that points to a specific quote from a specific + * File associated with the assistant or the message. Generated when the assistant + * uses the "retrieval" tool to search files. */ -export interface MessageContentImageFile { - image_file: MessageContentImageFile.ImageFile; +export interface FileCitationAnnotation { + end_index: number; + + file_citation: FileCitationAnnotation.FileCitation; + + start_index: number; /** - * Always `image_file`. + * The text in the message content that needs to be replaced. */ - type: 'image_file'; + text: string; + + /** + * Always `file_citation`. + */ + type: 'file_citation'; } -export namespace MessageContentImageFile { - export interface ImageFile { +export namespace FileCitationAnnotation { + export interface FileCitation { /** - * The [File](https://platform.openai.com/docs/api-reference/files) ID of the image - * in the message content. + * The ID of the specific File the citation is from. */ file_id: string; + + /** + * The specific quote in the file. + */ + quote: string; } } /** - * The text content that is part of a message. + * A citation within the message that points to a specific quote from a specific + * File associated with the assistant or the message. Generated when the assistant + * uses the "retrieval" tool to search files. */ -export interface MessageContentText { - text: MessageContentText.Text; +export interface FileCitationDeltaAnnotation { + /** + * The index of the annotation in the text content part. + */ + index: number; /** - * Always `text`. + * Always `file_citation`. */ - type: 'text'; + type: 'file_citation'; + + end_index?: number; + + file_citation?: FileCitationDeltaAnnotation.FileCitation; + + start_index?: number; + + /** + * The text in the message content that needs to be replaced. + */ + text?: string; } -export namespace MessageContentText { - export interface Text { - annotations: Array; +export namespace FileCitationDeltaAnnotation { + export interface FileCitation { + /** + * The ID of the specific File the citation is from. + */ + file_id?: string; /** - * The data that makes up the text. + * The specific quote in the file. */ - value: string; + quote?: string; } +} + +/** + * A URL for the file that's generated when the assistant used the + * `code_interpreter` tool to generate a file. + */ +export interface FilePathAnnotation { + end_index: number; + + file_path: FilePathAnnotation.FilePath; + + start_index: number; + + /** + * The text in the message content that needs to be replaced. + */ + text: string; + + /** + * Always `file_path`. + */ + type: 'file_path'; +} - export namespace Text { +export namespace FilePathAnnotation { + export interface FilePath { /** - * A citation within the message that points to a specific quote from a specific - * File associated with the assistant or the message. Generated when the assistant - * uses the "retrieval" tool to search files. + * The ID of the file that was generated. */ - export interface FileCitation { - end_index: number; + file_id: string; + } +} + +/** + * A URL for the file that's generated when the assistant used the + * `code_interpreter` tool to generate a file. + */ +export interface FilePathDeltaAnnotation { + /** + * The index of the annotation in the text content part. + */ + index: number; - file_citation: FileCitation.FileCitation; + /** + * Always `file_path`. + */ + type: 'file_path'; - start_index: number; + end_index?: number; - /** - * The text in the message content that needs to be replaced. - */ - text: string; + file_path?: FilePathDeltaAnnotation.FilePath; - /** - * Always `file_citation`. - */ - type: 'file_citation'; - } + start_index?: number; - export namespace FileCitation { - export interface FileCitation { - /** - * The ID of the specific File the citation is from. - */ - file_id: string; - - /** - * The specific quote in the file. - */ - quote: string; - } - } + /** + * The text in the message content that needs to be replaced. + */ + text?: string; +} +export namespace FilePathDeltaAnnotation { + export interface FilePath { /** - * A URL for the file that's generated when the assistant used the - * `code_interpreter` tool to generate a file. + * The ID of the file that was generated. */ - export interface FilePath { - end_index: number; + file_id?: string; + } +} - file_path: FilePath.FilePath; +export interface ImageFile { + /** + * The [File](https://platform.openai.com/docs/api-reference/files) ID of the image + * in the message content. + */ + file_id: string; +} - start_index: number; +/** + * References an image [File](https://platform.openai.com/docs/api-reference/files) + * in the content of a message. + */ +export interface ImageFileContentBlock { + image_file: ImageFile; - /** - * The text in the message content that needs to be replaced. - */ - text: string; + /** + * Always `image_file`. + */ + type: 'image_file'; +} - /** - * Always `file_path`. - */ - type: 'file_path'; - } +export interface ImageFileDelta { + /** + * The [File](https://platform.openai.com/docs/api-reference/files) ID of the image + * in the message content. + */ + file_id?: string; +} - export namespace FilePath { - export interface FilePath { - /** - * The ID of the file that was generated. - */ - file_id: string; - } - } - } +/** + * References an image [File](https://platform.openai.com/docs/api-reference/files) + * in the content of a message. + */ +export interface ImageFileDeltaBlock { + /** + * The index of the content part in the message. + */ + index: number; + + /** + * Always `image_file`. + */ + type: 'image_file'; + + image_file?: ImageFileDelta; } /** * Represents a message within a * [thread](https://platform.openai.com/docs/api-reference/threads). */ -export interface ThreadMessage { +export interface Message { /** * The identifier, which can be referenced in API endpoints. */ @@ -215,10 +302,15 @@ export interface ThreadMessage { */ assistant_id: string | null; + /** + * The Unix timestamp (in seconds) for when the message was completed. + */ + completed_at: number | null; + /** * The content of the message in array of text and/or images. */ - content: Array; + content: Array; /** * The Unix timestamp (in seconds) for when the message was created. @@ -232,6 +324,16 @@ export interface ThreadMessage { */ file_ids: Array; + /** + * The Unix timestamp (in seconds) for when the message was marked as incomplete. + */ + incomplete_at: number | null; + + /** + * On an incomplete message, details about why the message is incomplete. + */ + incomplete_details: Message.IncompleteDetails | null; + /** * Set of 16 key-value pairs that can be attached to an object. This can be useful * for storing additional information about the object in a structured format. Keys @@ -257,6 +359,12 @@ export interface ThreadMessage { */ run_id: string | null; + /** + * The status of the message, which can be either `in_progress`, `incomplete`, or + * `completed`. + */ + status: 'in_progress' | 'incomplete' | 'completed'; + /** * The [thread](https://platform.openai.com/docs/api-reference/threads) ID that * this message belongs to. @@ -264,7 +372,31 @@ export interface ThreadMessage { thread_id: string; } -export interface ThreadMessageDeleted { +export namespace Message { + /** + * On an incomplete message, details about why the message is incomplete. + */ + export interface IncompleteDetails { + /** + * The reason the message is incomplete. + */ + reason: 'content_filter' | 'max_tokens' | 'run_cancelled' | 'run_expired' | 'run_failed'; + } +} + +/** + * References an image [File](https://platform.openai.com/docs/api-reference/files) + * in the content of a message. + */ +export type MessageContent = ImageFileContentBlock | TextContentBlock; + +/** + * References an image [File](https://platform.openai.com/docs/api-reference/files) + * in the content of a message. + */ +export type MessageContentDelta = ImageFileDeltaBlock | TextDeltaBlock; + +export interface MessageDeleted { id: string; deleted: boolean; @@ -272,6 +404,96 @@ export interface ThreadMessageDeleted { object: 'thread.message.deleted'; } +/** + * The delta containing the fields that have changed on the Message. + */ +export interface MessageDelta { + /** + * The content of the message in array of text and/or images. + */ + content?: Array; + + /** + * A list of [file](https://platform.openai.com/docs/api-reference/files) IDs that + * the assistant should use. Useful for tools like retrieval and code_interpreter + * that can access files. A maximum of 10 files can be attached to a message. + */ + file_ids?: Array; + + /** + * The entity that produced the message. One of `user` or `assistant`. + */ + role?: 'user' | 'assistant'; +} + +/** + * Represents a message delta i.e. any changed fields on a message during + * streaming. + */ +export interface MessageDeltaEvent { + /** + * The identifier of the message, which can be referenced in API endpoints. + */ + id: string; + + /** + * The delta containing the fields that have changed on the Message. + */ + delta: MessageDelta; + + /** + * The object type, which is always `thread.message.delta`. + */ + object: 'thread.message.delta'; +} + +export interface Text { + annotations: Array; + + /** + * The data that makes up the text. + */ + value: string; +} + +/** + * The text content that is part of a message. + */ +export interface TextContentBlock { + text: Text; + + /** + * Always `text`. + */ + type: 'text'; +} + +export interface TextDelta { + annotations?: Array; + + /** + * The data that makes up the text. + */ + value?: string; +} + +/** + * The text content that is part of a message. + */ +export interface TextDeltaBlock { + /** + * The index of the content part in the message. + */ + index: number; + + /** + * Always `text`. + */ + type: 'text'; + + text?: TextDelta; +} + export interface MessageCreateParams { /** * The content of the message. @@ -328,11 +550,27 @@ export interface MessageListParams extends CursorPageParams { } export namespace Messages { - export import MessageContentImageFile = MessagesAPI.MessageContentImageFile; - export import MessageContentText = MessagesAPI.MessageContentText; - export import ThreadMessage = MessagesAPI.ThreadMessage; - export import ThreadMessageDeleted = MessagesAPI.ThreadMessageDeleted; - export import ThreadMessagesPage = MessagesAPI.ThreadMessagesPage; + export import Annotation = MessagesAPI.Annotation; + export import AnnotationDelta = MessagesAPI.AnnotationDelta; + export import FileCitationAnnotation = MessagesAPI.FileCitationAnnotation; + export import FileCitationDeltaAnnotation = MessagesAPI.FileCitationDeltaAnnotation; + export import FilePathAnnotation = MessagesAPI.FilePathAnnotation; + export import FilePathDeltaAnnotation = MessagesAPI.FilePathDeltaAnnotation; + export import ImageFile = MessagesAPI.ImageFile; + export import ImageFileContentBlock = MessagesAPI.ImageFileContentBlock; + export import ImageFileDelta = MessagesAPI.ImageFileDelta; + export import ImageFileDeltaBlock = MessagesAPI.ImageFileDeltaBlock; + export import Message = MessagesAPI.Message; + export import MessageContent = MessagesAPI.MessageContent; + export import MessageContentDelta = MessagesAPI.MessageContentDelta; + export import MessageDeleted = MessagesAPI.MessageDeleted; + export import MessageDelta = MessagesAPI.MessageDelta; + export import MessageDeltaEvent = MessagesAPI.MessageDeltaEvent; + export import Text = MessagesAPI.Text; + export import TextContentBlock = MessagesAPI.TextContentBlock; + export import TextDelta = MessagesAPI.TextDelta; + export import TextDeltaBlock = MessagesAPI.TextDeltaBlock; + export import MessagesPage = MessagesAPI.MessagesPage; export import MessageCreateParams = MessagesAPI.MessageCreateParams; export import MessageUpdateParams = MessagesAPI.MessageUpdateParams; export import MessageListParams = MessagesAPI.MessageListParams; diff --git a/src/resources/beta/threads/runs/index.ts b/src/resources/beta/threads/runs/index.ts index b11736c5c..7fa34637a 100644 --- a/src/resources/beta/threads/runs/index.ts +++ b/src/resources/beta/threads/runs/index.ts @@ -1,11 +1,22 @@ // File generated from our OpenAPI spec by Stainless. export { - CodeToolCall, + CodeInterpreterLogs, + CodeInterpreterOutputImage, + CodeInterpreterToolCall, + CodeInterpreterToolCallDelta, FunctionToolCall, + FunctionToolCallDelta, MessageCreationStepDetails, RetrievalToolCall, + RetrievalToolCallDelta, RunStep, + RunStepDelta, + RunStepDeltaEvent, + RunStepDeltaMessageDelta, + ToolCall, + ToolCallDelta, + ToolCallDeltaObject, ToolCallsStepDetails, StepListParams, RunStepsPage, @@ -16,9 +27,15 @@ export { Run, RunStatus, RunCreateParams, + RunCreateParamsNonStreaming, + RunCreateParamsStreaming, RunUpdateParams, RunListParams, + RunCreateAndStreamParams, RunSubmitToolOutputsParams, + RunSubmitToolOutputsParamsNonStreaming, + RunSubmitToolOutputsParamsStreaming, + RunSubmitToolOutputsStreamParams, RunsPage, Runs, } from './runs'; diff --git a/src/resources/beta/threads/runs/runs.ts b/src/resources/beta/threads/runs/runs.ts index 9a0bc00dd..8fe09ecc6 100644 --- a/src/resources/beta/threads/runs/runs.ts +++ b/src/resources/beta/threads/runs/runs.ts @@ -1,12 +1,16 @@ // File generated from our OpenAPI spec by Stainless. import * as Core from 'openai/core'; +import { APIPromise } from 'openai/core'; import { APIResource } from 'openai/resource'; import { isRequestOptions } from 'openai/core'; +import { AssistantStream, RunCreateParamsBaseStream } from 'openai/lib/AssistantStream'; +import { RunSubmitToolOutputsParamsStream } from 'openai/lib/AssistantStream'; import * as RunsAPI from 'openai/resources/beta/threads/runs/runs'; -import * as Shared from 'openai/resources/shared'; +import * as AssistantsAPI from 'openai/resources/beta/assistants/assistants'; import * as StepsAPI from 'openai/resources/beta/threads/runs/steps'; import { CursorPage, type CursorPageParams } from 'openai/pagination'; +import { Stream } from 'openai/streaming'; export class Runs extends APIResource { steps: StepsAPI.Steps = new StepsAPI.Steps(this._client); @@ -14,12 +18,28 @@ export class Runs extends APIResource { /** * Create a run. */ - create(threadId: string, body: RunCreateParams, options?: Core.RequestOptions): Core.APIPromise { + create(threadId: string, body: RunCreateParamsNonStreaming, options?: Core.RequestOptions): APIPromise; + create( + threadId: string, + body: RunCreateParamsStreaming, + options?: Core.RequestOptions, + ): APIPromise>; + create( + threadId: string, + body: RunCreateParamsBase, + options?: Core.RequestOptions, + ): APIPromise | Run>; + create( + threadId: string, + body: RunCreateParams, + options?: Core.RequestOptions, + ): APIPromise | APIPromise> { return this._client.post(`/threads/${threadId}/runs`, { body, ...options, headers: { 'OpenAI-Beta': 'assistants=v1', ...options?.headers }, - }); + stream: body.stream ?? false, + }) as APIPromise | APIPromise>; } /** @@ -82,23 +102,72 @@ export class Runs extends APIResource { }); } + /** + * Create a Run stream + */ + createAndStream( + threadId: string, + body: RunCreateParamsBaseStream, + options?: Core.RequestOptions, + ): AssistantStream { + return AssistantStream.createAssistantStream(threadId, this._client.beta.threads.runs, body, options); + } + /** * When a run has the `status: "requires_action"` and `required_action.type` is * `submit_tool_outputs`, this endpoint can be used to submit the outputs from the * tool calls once they're all completed. All outputs must be submitted in a single * request. */ + submitToolOutputs( + threadId: string, + runId: string, + body: RunSubmitToolOutputsParamsNonStreaming, + options?: Core.RequestOptions, + ): APIPromise; + submitToolOutputs( + threadId: string, + runId: string, + body: RunSubmitToolOutputsParamsStreaming, + options?: Core.RequestOptions, + ): APIPromise>; + submitToolOutputs( + threadId: string, + runId: string, + body: RunSubmitToolOutputsParamsBase, + options?: Core.RequestOptions, + ): APIPromise | Run>; submitToolOutputs( threadId: string, runId: string, body: RunSubmitToolOutputsParams, options?: Core.RequestOptions, - ): Core.APIPromise { + ): APIPromise | APIPromise> { return this._client.post(`/threads/${threadId}/runs/${runId}/submit_tool_outputs`, { body, ...options, headers: { 'OpenAI-Beta': 'assistants=v1', ...options?.headers }, - }); + stream: body.stream ?? false, + }) as APIPromise | APIPromise>; + } + + /** + * Submit the tool outputs from a previous run and stream the run to a terminal + * state. + */ + submitToolOutputsStream( + threadId: string, + runId: string, + body: RunSubmitToolOutputsParamsStream, + options?: Core.RequestOptions, + ): AssistantStream { + return AssistantStream.createToolAssistantStream( + threadId, + runId, + this._client.beta.threads.runs, + body, + options, + ); } } @@ -180,7 +249,7 @@ export interface Run { /** * The Unix timestamp (in seconds) for when the run will expire. */ - expires_at: number; + expires_at: number | null; /** * The Unix timestamp (in seconds) for when the run failed. @@ -255,7 +324,7 @@ export interface Run { * [assistant](https://platform.openai.com/docs/api-reference/assistants) used for * this run. */ - tools: Array; + tools: Array; /** * Usage statistics related to the run. This value will be `null` if the run is not @@ -308,29 +377,6 @@ export namespace Run { } } - export interface AssistantToolsCode { - /** - * The type of tool being defined: `code_interpreter` - */ - type: 'code_interpreter'; - } - - export interface AssistantToolsRetrieval { - /** - * The type of tool being defined: `retrieval` - */ - type: 'retrieval'; - } - - export interface AssistantToolsFunction { - function: Shared.FunctionDefinition; - - /** - * The type of tool being defined: `function` - */ - type: 'function'; - } - /** * Usage statistics related to the run. This value will be `null` if the run is not * in a terminal state (i.e. `in_progress`, `queued`, etc.). @@ -368,7 +414,9 @@ export type RunStatus = | 'completed' | 'expired'; -export interface RunCreateParams { +export type RunCreateParams = RunCreateParamsNonStreaming | RunCreateParamsStreaming; + +export interface RunCreateParamsBase { /** * The ID of the * [assistant](https://platform.openai.com/docs/api-reference/assistants) to use to @@ -406,40 +454,41 @@ export interface RunCreateParams { */ model?: string | null; + /** + * If `true`, returns a stream of events that happen during the Run as server-sent + * events, terminating when the Run enters a terminal state with a `data: [DONE]` + * message. + */ + stream?: boolean | null; + /** * Override the tools the assistant can use for this run. This is useful for * modifying the behavior on a per-run basis. */ - tools?: Array< - | RunCreateParams.AssistantToolsCode - | RunCreateParams.AssistantToolsRetrieval - | RunCreateParams.AssistantToolsFunction - > | null; + tools?: Array | null; } export namespace RunCreateParams { - export interface AssistantToolsCode { - /** - * The type of tool being defined: `code_interpreter` - */ - type: 'code_interpreter'; - } - - export interface AssistantToolsRetrieval { - /** - * The type of tool being defined: `retrieval` - */ - type: 'retrieval'; - } + export type RunCreateParamsNonStreaming = RunsAPI.RunCreateParamsNonStreaming; + export type RunCreateParamsStreaming = RunsAPI.RunCreateParamsStreaming; +} - export interface AssistantToolsFunction { - function: Shared.FunctionDefinition; +export interface RunCreateParamsNonStreaming extends RunCreateParamsBase { + /** + * If `true`, returns a stream of events that happen during the Run as server-sent + * events, terminating when the Run enters a terminal state with a `data: [DONE]` + * message. + */ + stream?: false | null; +} - /** - * The type of tool being defined: `function` - */ - type: 'function'; - } +export interface RunCreateParamsStreaming extends RunCreateParamsBase { + /** + * If `true`, returns a stream of events that happen during the Run as server-sent + * events, terminating when the Run enters a terminal state with a `data: [DONE]` + * message. + */ + stream: true; } export interface RunUpdateParams { @@ -468,11 +517,67 @@ export interface RunListParams extends CursorPageParams { order?: 'asc' | 'desc'; } -export interface RunSubmitToolOutputsParams { +export interface RunCreateAndStreamParams { + /** + * The ID of the + * [assistant](https://platform.openai.com/docs/api-reference/assistants) to use to + * execute this run. + */ + assistant_id: string; + + /** + * Appends additional instructions at the end of the instructions for the run. This + * is useful for modifying the behavior on a per-run basis without overriding other + * instructions. + */ + additional_instructions?: string | null; + + /** + * Overrides the + * [instructions](https://platform.openai.com/docs/api-reference/assistants/createAssistant) + * of the assistant. This is useful for modifying the behavior on a per-run basis. + */ + instructions?: string | null; + + /** + * Set of 16 key-value pairs that can be attached to an object. This can be useful + * for storing additional information about the object in a structured format. Keys + * can be a maximum of 64 characters long and values can be a maxium of 512 + * characters long. + */ + metadata?: unknown | null; + + /** + * The ID of the [Model](https://platform.openai.com/docs/api-reference/models) to + * be used to execute this run. If a value is provided here, it will override the + * model associated with the assistant. If not, the model associated with the + * assistant will be used. + */ + model?: string | null; + + /** + * Override the tools the assistant can use for this run. This is useful for + * modifying the behavior on a per-run basis. + */ + tools?: Array | null; +} + +export type RunSubmitToolOutputsParams = + | RunSubmitToolOutputsParamsNonStreaming + | RunSubmitToolOutputsParamsStreaming; + +export interface RunSubmitToolOutputsParamsBase { /** * A list of tools for which the outputs are being submitted. */ tool_outputs: Array; + + /** + * If `true`, returns a stream of events that happen during the Run as server-sent + * events, terminating when the Run enters a terminal state with a `data: [DONE]` + * message. + */ + stream?: boolean | null; } export namespace RunSubmitToolOutputsParams { @@ -488,6 +593,49 @@ export namespace RunSubmitToolOutputsParams { */ tool_call_id?: string; } + + export type RunSubmitToolOutputsParamsNonStreaming = RunsAPI.RunSubmitToolOutputsParamsNonStreaming; + export type RunSubmitToolOutputsParamsStreaming = RunsAPI.RunSubmitToolOutputsParamsStreaming; +} + +export interface RunSubmitToolOutputsParamsNonStreaming extends RunSubmitToolOutputsParamsBase { + /** + * If `true`, returns a stream of events that happen during the Run as server-sent + * events, terminating when the Run enters a terminal state with a `data: [DONE]` + * message. + */ + stream?: false | null; +} + +export interface RunSubmitToolOutputsParamsStreaming extends RunSubmitToolOutputsParamsBase { + /** + * If `true`, returns a stream of events that happen during the Run as server-sent + * events, terminating when the Run enters a terminal state with a `data: [DONE]` + * message. + */ + stream: true; +} + +export interface RunSubmitToolOutputsStreamParams { + /** + * A list of tools for which the outputs are being submitted. + */ + tool_outputs: Array; +} + +export namespace RunSubmitToolOutputsStreamParams { + export interface ToolOutput { + /** + * The output of the tool call to be submitted to continue the run. + */ + output?: string; + + /** + * The ID of the tool call in the `required_action` object within the run object + * the output is being submitted for. + */ + tool_call_id?: string; + } } export namespace Runs { @@ -496,15 +644,32 @@ export namespace Runs { export import RunStatus = RunsAPI.RunStatus; export import RunsPage = RunsAPI.RunsPage; export import RunCreateParams = RunsAPI.RunCreateParams; + export import RunCreateParamsNonStreaming = RunsAPI.RunCreateParamsNonStreaming; + export import RunCreateParamsStreaming = RunsAPI.RunCreateParamsStreaming; export import RunUpdateParams = RunsAPI.RunUpdateParams; export import RunListParams = RunsAPI.RunListParams; + export import RunCreateAndStreamParams = RunsAPI.RunCreateAndStreamParams; export import RunSubmitToolOutputsParams = RunsAPI.RunSubmitToolOutputsParams; + export import RunSubmitToolOutputsParamsNonStreaming = RunsAPI.RunSubmitToolOutputsParamsNonStreaming; + export import RunSubmitToolOutputsParamsStreaming = RunsAPI.RunSubmitToolOutputsParamsStreaming; + export import RunSubmitToolOutputsStreamParams = RunsAPI.RunSubmitToolOutputsStreamParams; export import Steps = StepsAPI.Steps; - export import CodeToolCall = StepsAPI.CodeToolCall; + export import CodeInterpreterLogs = StepsAPI.CodeInterpreterLogs; + export import CodeInterpreterOutputImage = StepsAPI.CodeInterpreterOutputImage; + export import CodeInterpreterToolCall = StepsAPI.CodeInterpreterToolCall; + export import CodeInterpreterToolCallDelta = StepsAPI.CodeInterpreterToolCallDelta; export import FunctionToolCall = StepsAPI.FunctionToolCall; + export import FunctionToolCallDelta = StepsAPI.FunctionToolCallDelta; export import MessageCreationStepDetails = StepsAPI.MessageCreationStepDetails; export import RetrievalToolCall = StepsAPI.RetrievalToolCall; + export import RetrievalToolCallDelta = StepsAPI.RetrievalToolCallDelta; export import RunStep = StepsAPI.RunStep; + export import RunStepDelta = StepsAPI.RunStepDelta; + export import RunStepDeltaEvent = StepsAPI.RunStepDeltaEvent; + export import RunStepDeltaMessageDelta = StepsAPI.RunStepDeltaMessageDelta; + export import ToolCall = StepsAPI.ToolCall; + export import ToolCallDelta = StepsAPI.ToolCallDelta; + export import ToolCallDeltaObject = StepsAPI.ToolCallDeltaObject; export import ToolCallsStepDetails = StepsAPI.ToolCallsStepDetails; export import RunStepsPage = StepsAPI.RunStepsPage; export import StepListParams = StepsAPI.StepListParams; diff --git a/src/resources/beta/threads/runs/steps.ts b/src/resources/beta/threads/runs/steps.ts index c574c94d1..4218e9769 100644 --- a/src/resources/beta/threads/runs/steps.ts +++ b/src/resources/beta/threads/runs/steps.ts @@ -55,10 +55,54 @@ export class Steps extends APIResource { export class RunStepsPage extends CursorPage {} +/** + * Text output from the Code Interpreter tool call as part of a run step. + */ +export interface CodeInterpreterLogs { + /** + * The index of the output in the outputs array. + */ + index: number; + + /** + * Always `logs`. + */ + type: 'logs'; + + /** + * The text output from the Code Interpreter tool call. + */ + logs?: string; +} + +export interface CodeInterpreterOutputImage { + /** + * The index of the output in the outputs array. + */ + index: number; + + /** + * Always `image`. + */ + type: 'image'; + + image?: CodeInterpreterOutputImage.Image; +} + +export namespace CodeInterpreterOutputImage { + export interface Image { + /** + * The [file](https://platform.openai.com/docs/api-reference/files) ID of the + * image. + */ + file_id?: string; + } +} + /** * Details of the Code Interpreter tool call the run step was involved in. */ -export interface CodeToolCall { +export interface CodeInterpreterToolCall { /** * The ID of the tool call. */ @@ -67,7 +111,7 @@ export interface CodeToolCall { /** * The Code Interpreter tool call definition. */ - code_interpreter: CodeToolCall.CodeInterpreter; + code_interpreter: CodeInterpreterToolCall.CodeInterpreter; /** * The type of tool call. This is always going to be `code_interpreter` for this @@ -76,7 +120,7 @@ export interface CodeToolCall { type: 'code_interpreter'; } -export namespace CodeToolCall { +export namespace CodeInterpreterToolCall { /** * The Code Interpreter tool call definition. */ @@ -131,6 +175,51 @@ export namespace CodeToolCall { } } +/** + * Details of the Code Interpreter tool call the run step was involved in. + */ +export interface CodeInterpreterToolCallDelta { + /** + * The index of the tool call in the tool calls array. + */ + index: number; + + /** + * The type of tool call. This is always going to be `code_interpreter` for this + * type of tool call. + */ + type: 'code_interpreter'; + + /** + * The ID of the tool call. + */ + id?: string; + + /** + * The Code Interpreter tool call definition. + */ + code_interpreter?: CodeInterpreterToolCallDelta.CodeInterpreter; +} + +export namespace CodeInterpreterToolCallDelta { + /** + * The Code Interpreter tool call definition. + */ + export interface CodeInterpreter { + /** + * The input to the Code Interpreter tool call. + */ + input?: string; + + /** + * The outputs from the Code Interpreter tool call. Code Interpreter can output one + * or more items, including text (`logs`) or images (`image`). Each of these are + * represented by a different object type. + */ + outputs?: Array; + } +} + export interface FunctionToolCall { /** * The ID of the tool call object. @@ -173,6 +262,53 @@ export namespace FunctionToolCall { } } +export interface FunctionToolCallDelta { + /** + * The index of the tool call in the tool calls array. + */ + index: number; + + /** + * The type of tool call. This is always going to be `function` for this type of + * tool call. + */ + type: 'function'; + + /** + * The ID of the tool call object. + */ + id?: string; + + /** + * The definition of the function that was called. + */ + function?: FunctionToolCallDelta.Function; +} + +export namespace FunctionToolCallDelta { + /** + * The definition of the function that was called. + */ + export interface Function { + /** + * The arguments passed to the function. + */ + arguments?: string; + + /** + * The name of the function. + */ + name?: string; + + /** + * The output of the function. This will be `null` if the outputs have not been + * [submitted](https://platform.openai.com/docs/api-reference/runs/submitToolOutputs) + * yet. + */ + output?: string | null; + } +} + /** * Details of the message creation by the run step. */ @@ -212,6 +348,29 @@ export interface RetrievalToolCall { type: 'retrieval'; } +export interface RetrievalToolCallDelta { + /** + * The index of the tool call in the tool calls array. + */ + index: number; + + /** + * The type of tool call. This is always going to be `retrieval` for this type of + * tool call. + */ + type: 'retrieval'; + + /** + * The ID of the tool call object. + */ + id?: string; + + /** + * For now, this is always going to be an empty object. + */ + retrieval?: unknown; +} + /** * Represents a step in execution of a run. */ @@ -347,6 +506,85 @@ export namespace RunStep { } } +/** + * The delta containing the fields that have changed on the run step. + */ +export interface RunStepDelta { + /** + * The details of the run step. + */ + step_details?: RunStepDeltaMessageDelta | ToolCallDeltaObject; +} + +/** + * Represents a run step delta i.e. any changed fields on a run step during + * streaming. + */ +export interface RunStepDeltaEvent { + /** + * The identifier of the run step, which can be referenced in API endpoints. + */ + id: string; + + /** + * The delta containing the fields that have changed on the run step. + */ + delta: RunStepDelta; + + /** + * The object type, which is always `thread.run.step.delta`. + */ + object: 'thread.run.step.delta'; +} + +/** + * Details of the message creation by the run step. + */ +export interface RunStepDeltaMessageDelta { + /** + * Always `message_creation`. + */ + type: 'message_creation'; + + message_creation?: RunStepDeltaMessageDelta.MessageCreation; +} + +export namespace RunStepDeltaMessageDelta { + export interface MessageCreation { + /** + * The ID of the message that was created by this run step. + */ + message_id?: string; + } +} + +/** + * Details of the Code Interpreter tool call the run step was involved in. + */ +export type ToolCall = CodeInterpreterToolCall | RetrievalToolCall | FunctionToolCall; + +/** + * Details of the Code Interpreter tool call the run step was involved in. + */ +export type ToolCallDelta = CodeInterpreterToolCallDelta | RetrievalToolCallDelta | FunctionToolCallDelta; + +/** + * Details of the tool call. + */ +export interface ToolCallDeltaObject { + /** + * Always `tool_calls`. + */ + type: 'tool_calls'; + + /** + * An array of tool calls the run step was involved in. These can be associated + * with one of three types of tools: `code_interpreter`, `retrieval`, or + * `function`. + */ + tool_calls?: Array; +} + /** * Details of the tool call. */ @@ -356,7 +594,7 @@ export interface ToolCallsStepDetails { * with one of three types of tools: `code_interpreter`, `retrieval`, or * `function`. */ - tool_calls: Array; + tool_calls: Array; /** * Always `tool_calls`. @@ -381,11 +619,22 @@ export interface StepListParams extends CursorPageParams { } export namespace Steps { - export import CodeToolCall = StepsAPI.CodeToolCall; + export import CodeInterpreterLogs = StepsAPI.CodeInterpreterLogs; + export import CodeInterpreterOutputImage = StepsAPI.CodeInterpreterOutputImage; + export import CodeInterpreterToolCall = StepsAPI.CodeInterpreterToolCall; + export import CodeInterpreterToolCallDelta = StepsAPI.CodeInterpreterToolCallDelta; export import FunctionToolCall = StepsAPI.FunctionToolCall; + export import FunctionToolCallDelta = StepsAPI.FunctionToolCallDelta; export import MessageCreationStepDetails = StepsAPI.MessageCreationStepDetails; export import RetrievalToolCall = StepsAPI.RetrievalToolCall; + export import RetrievalToolCallDelta = StepsAPI.RetrievalToolCallDelta; export import RunStep = StepsAPI.RunStep; + export import RunStepDelta = StepsAPI.RunStepDelta; + export import RunStepDeltaEvent = StepsAPI.RunStepDeltaEvent; + export import RunStepDeltaMessageDelta = StepsAPI.RunStepDeltaMessageDelta; + export import ToolCall = StepsAPI.ToolCall; + export import ToolCallDelta = StepsAPI.ToolCallDelta; + export import ToolCallDeltaObject = StepsAPI.ToolCallDeltaObject; export import ToolCallsStepDetails = StepsAPI.ToolCallsStepDetails; export import RunStepsPage = StepsAPI.RunStepsPage; export import StepListParams = StepsAPI.StepListParams; diff --git a/src/resources/beta/threads/threads.ts b/src/resources/beta/threads/threads.ts index 5aa1f8c25..cbde41f89 100644 --- a/src/resources/beta/threads/threads.ts +++ b/src/resources/beta/threads/threads.ts @@ -1,12 +1,15 @@ // File generated from our OpenAPI spec by Stainless. import * as Core from 'openai/core'; +import { APIPromise } from 'openai/core'; import { APIResource } from 'openai/resource'; import { isRequestOptions } from 'openai/core'; +import { AssistantStream, ThreadCreateAndRunParamsBaseStream } from 'openai/lib/AssistantStream'; import * as ThreadsAPI from 'openai/resources/beta/threads/threads'; -import * as Shared from 'openai/resources/shared'; +import * as AssistantsAPI from 'openai/resources/beta/assistants/assistants'; import * as MessagesAPI from 'openai/resources/beta/threads/messages/messages'; import * as RunsAPI from 'openai/resources/beta/threads/runs/runs'; +import { Stream } from 'openai/streaming'; export class Threads extends APIResource { runs: RunsAPI.Runs = new RunsAPI.Runs(this._client); @@ -65,12 +68,38 @@ export class Threads extends APIResource { /** * Create a thread and run it in one request. */ - createAndRun(body: ThreadCreateAndRunParams, options?: Core.RequestOptions): Core.APIPromise { + createAndRun( + body: ThreadCreateAndRunParamsNonStreaming, + options?: Core.RequestOptions, + ): APIPromise; + createAndRun( + body: ThreadCreateAndRunParamsStreaming, + options?: Core.RequestOptions, + ): APIPromise>; + createAndRun( + body: ThreadCreateAndRunParamsBase, + options?: Core.RequestOptions, + ): APIPromise | RunsAPI.Run>; + createAndRun( + body: ThreadCreateAndRunParams, + options?: Core.RequestOptions, + ): APIPromise | APIPromise> { return this._client.post('/threads/runs', { body, ...options, headers: { 'OpenAI-Beta': 'assistants=v1', ...options?.headers }, - }); + stream: body.stream ?? false, + }) as APIPromise | APIPromise>; + } + + /** + * Create a thread and stream the run back + */ + createAndRunStream( + body: ThreadCreateAndRunParamsBaseStream, + options?: Core.RequestOptions, + ): AssistantStream { + return AssistantStream.createThreadAssistantStream(body, this._client.beta.threads, options); } } @@ -168,7 +197,11 @@ export interface ThreadUpdateParams { metadata?: unknown | null; } -export interface ThreadCreateAndRunParams { +export type ThreadCreateAndRunParams = + | ThreadCreateAndRunParamsNonStreaming + | ThreadCreateAndRunParamsStreaming; + +export interface ThreadCreateAndRunParamsBase { /** * The ID of the * [assistant](https://platform.openai.com/docs/api-reference/assistants) to use to @@ -198,6 +231,13 @@ export interface ThreadCreateAndRunParams { */ model?: string | null; + /** + * If `true`, returns a stream of events that happen during the Run as server-sent + * events, terminating when the Run enters a terminal state with a `data: [DONE]` + * message. + */ + stream?: boolean | null; + /** * If no thread is provided, an empty thread will be created. */ @@ -208,9 +248,7 @@ export interface ThreadCreateAndRunParams { * modifying the behavior on a per-run basis. */ tools?: Array< - | ThreadCreateAndRunParams.AssistantToolsCode - | ThreadCreateAndRunParams.AssistantToolsRetrieval - | ThreadCreateAndRunParams.AssistantToolsFunction + AssistantsAPI.CodeInterpreterTool | AssistantsAPI.RetrievalTool | AssistantsAPI.FunctionTool > | null; } @@ -265,27 +303,121 @@ export namespace ThreadCreateAndRunParams { } } - export interface AssistantToolsCode { + export type ThreadCreateAndRunParamsNonStreaming = ThreadsAPI.ThreadCreateAndRunParamsNonStreaming; + export type ThreadCreateAndRunParamsStreaming = ThreadsAPI.ThreadCreateAndRunParamsStreaming; +} + +export interface ThreadCreateAndRunParamsNonStreaming extends ThreadCreateAndRunParamsBase { + /** + * If `true`, returns a stream of events that happen during the Run as server-sent + * events, terminating when the Run enters a terminal state with a `data: [DONE]` + * message. + */ + stream?: false | null; +} + +export interface ThreadCreateAndRunParamsStreaming extends ThreadCreateAndRunParamsBase { + /** + * If `true`, returns a stream of events that happen during the Run as server-sent + * events, terminating when the Run enters a terminal state with a `data: [DONE]` + * message. + */ + stream: true; +} + +export interface ThreadCreateAndRunStreamParams { + /** + * The ID of the + * [assistant](https://platform.openai.com/docs/api-reference/assistants) to use to + * execute this run. + */ + assistant_id: string; + + /** + * Override the default system message of the assistant. This is useful for + * modifying the behavior on a per-run basis. + */ + instructions?: string | null; + + /** + * Set of 16 key-value pairs that can be attached to an object. This can be useful + * for storing additional information about the object in a structured format. Keys + * can be a maximum of 64 characters long and values can be a maxium of 512 + * characters long. + */ + metadata?: unknown | null; + + /** + * The ID of the [Model](https://platform.openai.com/docs/api-reference/models) to + * be used to execute this run. If a value is provided here, it will override the + * model associated with the assistant. If not, the model associated with the + * assistant will be used. + */ + model?: string | null; + + /** + * If no thread is provided, an empty thread will be created. + */ + thread?: ThreadCreateAndRunStreamParams.Thread; + + /** + * Override the tools the assistant can use for this run. This is useful for + * modifying the behavior on a per-run basis. + */ + tools?: Array< + AssistantsAPI.CodeInterpreterTool | AssistantsAPI.RetrievalTool | AssistantsAPI.FunctionTool + > | null; +} + +export namespace ThreadCreateAndRunStreamParams { + /** + * If no thread is provided, an empty thread will be created. + */ + export interface Thread { /** - * The type of tool being defined: `code_interpreter` + * A list of [messages](https://platform.openai.com/docs/api-reference/messages) to + * start the thread with. */ - type: 'code_interpreter'; - } + messages?: Array; - export interface AssistantToolsRetrieval { /** - * The type of tool being defined: `retrieval` + * Set of 16 key-value pairs that can be attached to an object. This can be useful + * for storing additional information about the object in a structured format. Keys + * can be a maximum of 64 characters long and values can be a maxium of 512 + * characters long. */ - type: 'retrieval'; + metadata?: unknown | null; } - export interface AssistantToolsFunction { - function: Shared.FunctionDefinition; + export namespace Thread { + export interface Message { + /** + * The content of the message. + */ + content: string; + + /** + * The role of the entity that is creating the message. Currently only `user` is + * supported. + */ + role: 'user'; - /** - * The type of tool being defined: `function` - */ - type: 'function'; + /** + * A list of [File](https://platform.openai.com/docs/api-reference/files) IDs that + * the message should use. There can be a maximum of 10 files attached to a + * message. Useful for tools like `retrieval` and `code_interpreter` that can + * access and use files. + */ + file_ids?: Array; + + /** + * Set of 16 key-value pairs that can be attached to an object. This can be useful + * for storing additional information about the object in a structured format. Keys + * can be a maximum of 64 characters long and values can be a maxium of 512 + * characters long. + */ + metadata?: unknown | null; + } } } @@ -295,21 +427,46 @@ export namespace Threads { export import ThreadCreateParams = ThreadsAPI.ThreadCreateParams; export import ThreadUpdateParams = ThreadsAPI.ThreadUpdateParams; export import ThreadCreateAndRunParams = ThreadsAPI.ThreadCreateAndRunParams; + export import ThreadCreateAndRunParamsNonStreaming = ThreadsAPI.ThreadCreateAndRunParamsNonStreaming; + export import ThreadCreateAndRunParamsStreaming = ThreadsAPI.ThreadCreateAndRunParamsStreaming; + export import ThreadCreateAndRunStreamParams = ThreadsAPI.ThreadCreateAndRunStreamParams; export import Runs = RunsAPI.Runs; export import RequiredActionFunctionToolCall = RunsAPI.RequiredActionFunctionToolCall; export import Run = RunsAPI.Run; export import RunStatus = RunsAPI.RunStatus; export import RunsPage = RunsAPI.RunsPage; export import RunCreateParams = RunsAPI.RunCreateParams; + export import RunCreateParamsNonStreaming = RunsAPI.RunCreateParamsNonStreaming; + export import RunCreateParamsStreaming = RunsAPI.RunCreateParamsStreaming; export import RunUpdateParams = RunsAPI.RunUpdateParams; export import RunListParams = RunsAPI.RunListParams; + export import RunCreateAndStreamParams = RunsAPI.RunCreateAndStreamParams; export import RunSubmitToolOutputsParams = RunsAPI.RunSubmitToolOutputsParams; + export import RunSubmitToolOutputsParamsNonStreaming = RunsAPI.RunSubmitToolOutputsParamsNonStreaming; + export import RunSubmitToolOutputsParamsStreaming = RunsAPI.RunSubmitToolOutputsParamsStreaming; + export import RunSubmitToolOutputsStreamParams = RunsAPI.RunSubmitToolOutputsStreamParams; export import Messages = MessagesAPI.Messages; - export import MessageContentImageFile = MessagesAPI.MessageContentImageFile; - export import MessageContentText = MessagesAPI.MessageContentText; - export import ThreadMessage = MessagesAPI.ThreadMessage; - export import ThreadMessageDeleted = MessagesAPI.ThreadMessageDeleted; - export import ThreadMessagesPage = MessagesAPI.ThreadMessagesPage; + export import Annotation = MessagesAPI.Annotation; + export import AnnotationDelta = MessagesAPI.AnnotationDelta; + export import FileCitationAnnotation = MessagesAPI.FileCitationAnnotation; + export import FileCitationDeltaAnnotation = MessagesAPI.FileCitationDeltaAnnotation; + export import FilePathAnnotation = MessagesAPI.FilePathAnnotation; + export import FilePathDeltaAnnotation = MessagesAPI.FilePathDeltaAnnotation; + export import ImageFile = MessagesAPI.ImageFile; + export import ImageFileContentBlock = MessagesAPI.ImageFileContentBlock; + export import ImageFileDelta = MessagesAPI.ImageFileDelta; + export import ImageFileDeltaBlock = MessagesAPI.ImageFileDeltaBlock; + export import Message = MessagesAPI.Message; + export import MessageContent = MessagesAPI.MessageContent; + export import MessageContentDelta = MessagesAPI.MessageContentDelta; + export import MessageDeleted = MessagesAPI.MessageDeleted; + export import MessageDelta = MessagesAPI.MessageDelta; + export import MessageDeltaEvent = MessagesAPI.MessageDeltaEvent; + export import Text = MessagesAPI.Text; + export import TextContentBlock = MessagesAPI.TextContentBlock; + export import TextDelta = MessagesAPI.TextDelta; + export import TextDeltaBlock = MessagesAPI.TextDeltaBlock; + export import MessagesPage = MessagesAPI.MessagesPage; export import MessageCreateParams = MessagesAPI.MessageCreateParams; export import MessageUpdateParams = MessagesAPI.MessageUpdateParams; export import MessageListParams = MessagesAPI.MessageListParams; diff --git a/src/resources/chat/completions.ts b/src/resources/chat/completions.ts index c2d6da0be..41216a8e3 100644 --- a/src/resources/chat/completions.ts +++ b/src/resources/chat/completions.ts @@ -829,7 +829,7 @@ export interface ChatCompletionCreateParamsBase { /** * A list of tools the model may call. Currently, only functions are supported as a * tool. Use this to provide a list of functions the model may generate JSON inputs - * for. + * for. A max of 128 functions are supported. */ tools?: Array; diff --git a/src/resources/completions.ts b/src/resources/completions.ts index f3e262f5f..83ecb3e99 100644 --- a/src/resources/completions.ts +++ b/src/resources/completions.ts @@ -253,6 +253,8 @@ export interface CompletionCreateParamsBase { /** * The suffix that comes after a completion of inserted text. + * + * This parameter is only supported for `gpt-3.5-turbo-instruct`. */ suffix?: string | null; diff --git a/src/resources/shared.ts b/src/resources/shared.ts index 05ab66383..a6b2c11bd 100644 --- a/src/resources/shared.ts +++ b/src/resources/shared.ts @@ -1,5 +1,15 @@ // File generated from our OpenAPI spec by Stainless. +export interface ErrorObject { + code: string | null; + + message: string; + + param: string | null; + + type: string; +} + export interface FunctionDefinition { /** * The name of the function to be called. Must be a-z, A-Z, 0-9, or contain diff --git a/src/streaming.ts b/src/streaming.ts index f90c5d89a..c452737aa 100644 --- a/src/streaming.ts +++ b/src/streaming.ts @@ -78,6 +78,20 @@ export class Stream implements AsyncIterable { } yield data; + } else { + let data; + try { + data = JSON.parse(sse.data); + } catch (e) { + console.error(`Could not parse message into JSON:`, sse.data); + console.error(`From chunk:`, sse.raw); + throw e; + } + // TODO: Is this where the error should be thrown? + if (sse.event == 'error') { + throw new APIError(undefined, data.error, data.message, undefined); + } + yield { event: sse.event, data: data } as any; } } done = true; diff --git a/tests/api-resources/beta/threads/runs/runs.test.ts b/tests/api-resources/beta/threads/runs/runs.test.ts index 5a720afce..45f17040a 100644 --- a/tests/api-resources/beta/threads/runs/runs.test.ts +++ b/tests/api-resources/beta/threads/runs/runs.test.ts @@ -27,6 +27,7 @@ describe('resource runs', () => { instructions: 'string', metadata: {}, model: 'string', + stream: false, tools: [{ type: 'code_interpreter' }, { type: 'code_interpreter' }, { type: 'code_interpreter' }], }); }); @@ -127,6 +128,7 @@ describe('resource runs', () => { { tool_call_id: 'string', output: 'string' }, { tool_call_id: 'string', output: 'string' }, ], + stream: false, }); }); }); diff --git a/tests/api-resources/beta/threads/threads.test.ts b/tests/api-resources/beta/threads/threads.test.ts index fc9fef723..9243dc11c 100644 --- a/tests/api-resources/beta/threads/threads.test.ts +++ b/tests/api-resources/beta/threads/threads.test.ts @@ -108,6 +108,7 @@ describe('resource threads', () => { instructions: 'string', metadata: {}, model: 'string', + stream: false, thread: { messages: [ { role: 'user', content: 'x', file_ids: ['string'], metadata: {} }, diff --git a/tests/streaming/assistants/assistant.test.ts b/tests/streaming/assistants/assistant.test.ts new file mode 100644 index 000000000..e8db3d585 --- /dev/null +++ b/tests/streaming/assistants/assistant.test.ts @@ -0,0 +1,32 @@ +import OpenAI from 'openai'; +import { AssistantStream } from 'openai/lib/AssistantStream'; + +const openai = new OpenAI({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('assistant tests', () => { + test('delta accumulation', () => { + expect(AssistantStream.accumulateDelta({}, {})).toEqual({}); + expect(AssistantStream.accumulateDelta({}, { a: 'apple' })).toEqual({ a: 'apple' }); + + // strings + expect(AssistantStream.accumulateDelta({ a: 'foo' }, { a: ' bar' })).toEqual({ a: 'foo bar' }); + + // dictionaries + expect(AssistantStream.accumulateDelta({ a: { foo: '1' } }, { a: { bar: '2' } })).toEqual({ + a: { + foo: '1', + bar: '2', + }, + }); + expect(AssistantStream.accumulateDelta({ a: { foo: 'hello,' } }, { a: { foo: ' world' } })).toEqual({ + a: { foo: 'hello, world' }, + }); + + expect(AssistantStream.accumulateDelta({}, { a: null })).toEqual({ a: null }); + expect(AssistantStream.accumulateDelta({ a: null }, { a: 'apple' })).toEqual({ a: 'apple' }); + expect(AssistantStream.accumulateDelta({ a: null }, { a: null })).toEqual({ a: null }); + }); +});