| import type { FinishReason } from '../types'; | ||
| export function isToolExecutionAllowedFinishReason( | ||
| finishReason: FinishReason, | ||
| ): boolean { | ||
| return finishReason === 'stop' || finishReason === 'tool-calls'; | ||
| } |
@@ -167,3 +167,3 @@ "use strict"; | ||
| // src/version.ts | ||
| var VERSION = true ? "6.0.259" : "0.0.0-test"; | ||
| var VERSION = true ? "6.0.260" : "0.0.0-test"; | ||
@@ -170,0 +170,0 @@ // src/util/download/download.ts |
@@ -147,3 +147,3 @@ // internal/index.ts | ||
| // src/version.ts | ||
| var VERSION = true ? "6.0.259" : "0.0.0-test"; | ||
| var VERSION = true ? "6.0.260" : "0.0.0-test"; | ||
@@ -150,0 +150,0 @@ // src/util/download/download.ts |
+1
-1
| { | ||
| "name": "ai", | ||
| "version": "6.0.259", | ||
| "version": "6.0.260", | ||
| "description": "AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.", | ||
@@ -5,0 +5,0 @@ "license": "Apache-2.0", |
@@ -67,2 +67,8 @@ import type { | ||
| async function markPromiseAsHandled<T>(promise: Promise<T>): Promise<void> { | ||
| try { | ||
| await promise; | ||
| } catch {} | ||
| } | ||
| /** | ||
@@ -590,2 +596,3 @@ * Callback that is set using the `onError` option. | ||
| let error: unknown | undefined; | ||
| let terminalError: { error: unknown } | undefined; | ||
@@ -703,2 +710,15 @@ // pipe chunks through a transformation stream that extracts metadata: | ||
| case 'error': { | ||
| if (terminalError === undefined) { | ||
| const wrappedError = wrapGatewayError(chunk.error); | ||
| terminalError = { error: wrappedError }; | ||
| error = wrappedError; | ||
| finishReason = 'error'; | ||
| self.rejectResultPromises(wrappedError); | ||
| } | ||
| controller.enqueue(chunk); | ||
| break; | ||
| } | ||
| case 'finish': { | ||
@@ -711,3 +731,6 @@ // send final text delta: | ||
| // store finish reason for telemetry: | ||
| finishReason = chunk.finishReason.unified; | ||
| finishReason = | ||
| terminalError === undefined | ||
| ? chunk.finishReason.unified | ||
| : 'error'; | ||
@@ -720,3 +743,3 @@ // store usage and metadata for promises and onFinish callback: | ||
| ...chunk, | ||
| finishReason: chunk.finishReason.unified, | ||
| finishReason, | ||
| usage, | ||
@@ -733,2 +756,6 @@ response: fullResponse, | ||
| if (terminalError !== undefined) { | ||
| break; | ||
| } | ||
| // resolve promises that can be resolved now: | ||
@@ -855,6 +882,14 @@ self._usage.resolve(usage); | ||
| stitchableStream.addStream(transformedStream); | ||
| stitchableStream.addStream(transformedStream, { | ||
| onError(error) { | ||
| const wrappedError = wrapGatewayError(error); | ||
| self.rejectResultPromises(wrappedError); | ||
| void onError({ error: wrappedError }); | ||
| }, | ||
| }); | ||
| }, | ||
| }) | ||
| .catch(error => { | ||
| self.rejectResultPromises(error); | ||
| // add an empty stream with an error to break the stream: | ||
@@ -877,2 +912,25 @@ stitchableStream.addStream( | ||
| private rejectResultPromises(error: unknown) { | ||
| this.rejectResultPromise({ delayedPromise: this._object, error }); | ||
| this.rejectResultPromise({ delayedPromise: this._usage, error }); | ||
| this.rejectResultPromise({ delayedPromise: this._providerMetadata, error }); | ||
| this.rejectResultPromise({ delayedPromise: this._warnings, error }); | ||
| this.rejectResultPromise({ delayedPromise: this._request, error }); | ||
| this.rejectResultPromise({ delayedPromise: this._response, error }); | ||
| this.rejectResultPromise({ delayedPromise: this._finishReason, error }); | ||
| } | ||
| private rejectResultPromise<T>({ | ||
| delayedPromise, | ||
| error, | ||
| }: { | ||
| delayedPromise: DelayedPromise<T>; | ||
| error: unknown; | ||
| }) { | ||
| if (delayedPromise.isPending()) { | ||
| delayedPromise.reject(error); | ||
| markPromiseAsHandled(delayedPromise.promise); | ||
| } | ||
| } | ||
| get object() { | ||
@@ -879,0 +937,0 @@ return this._object.promise; |
@@ -74,2 +74,3 @@ import type { | ||
| import { isApprovalNeeded } from './is-approval-needed'; | ||
| import { isToolExecutionAllowedFinishReason } from './is-tool-execution-allowed-finish-reason'; | ||
| import { maybeSignApproval } from './tool-approval-signature'; | ||
@@ -1035,3 +1036,8 @@ import { validateApprovedToolApprovals } from './validate-tool-approvals'; | ||
| if (stepToolSet != null) { | ||
| if ( | ||
| stepToolSet != null && | ||
| isToolExecutionAllowedFinishReason( | ||
| currentModelResponse.finishReason.unified, | ||
| ) | ||
| ) { | ||
| clientToolOutputs.push( | ||
@@ -1038,0 +1044,0 @@ ...(await executeTools({ |
@@ -31,2 +31,3 @@ import type { | ||
| import { isApprovalNeeded } from './is-approval-needed'; | ||
| import { isToolExecutionAllowedFinishReason } from './is-tool-execution-allowed-finish-reason'; | ||
| import { maybeSignApproval } from './tool-approval-signature'; | ||
@@ -204,2 +205,3 @@ import { parseToolCall } from './parse-tool-call'; | ||
| const outstandingToolResults = new Set<string>(); | ||
| const toolCallsToExecute: Array<TypedToolCall<TOOLS>> = []; | ||
@@ -229,2 +231,37 @@ // keep track of parsed tool calls so provider-emitted approval requests can reference them | ||
| function executeToolCallAfterFinish(toolCall: TypedToolCall<TOOLS>) { | ||
| const toolExecutionId = generateId(); // use our own id to guarantee uniqueness | ||
| outstandingToolResults.add(toolExecutionId); | ||
| executeToolCall({ | ||
| toolCall, | ||
| tools, | ||
| tracer, | ||
| telemetry, | ||
| messages, | ||
| abortSignal, | ||
| experimental_context, | ||
| stepNumber, | ||
| model, | ||
| onToolCallStart, | ||
| onToolCallFinish, | ||
| onPreliminaryToolResult: result => { | ||
| enqueueToolResult(result); | ||
| }, | ||
| }) | ||
| .then(result => { | ||
| enqueueToolResult(result); | ||
| }) | ||
| .catch(error => { | ||
| enqueueToolResult({ | ||
| type: 'error', | ||
| error, | ||
| }); | ||
| }) | ||
| .finally(() => { | ||
| outstandingToolResults.delete(toolExecutionId); | ||
| attemptClose(); | ||
| }); | ||
| } | ||
| // forward stream | ||
@@ -285,2 +322,11 @@ const forwardStream = new TransformStream< | ||
| }; | ||
| if (isToolExecutionAllowedFinishReason(chunk.finishReason.unified)) { | ||
| for (const toolCall of toolCallsToExecute.splice(0)) { | ||
| executeToolCallAfterFinish(toolCall); | ||
| } | ||
| } else { | ||
| toolCallsToExecute.length = 0; | ||
| } | ||
| break; | ||
@@ -322,3 +368,3 @@ } | ||
| toolCallsByToolCallId.set(toolCall.toolCallId, toolCall); | ||
| controller.enqueue(toolCall); | ||
| controller.enqueue({ ...toolCall }); | ||
@@ -389,37 +435,3 @@ if (toolCall.invalid) { | ||
| if (tool.execute != null && toolCall.providerExecuted !== true) { | ||
| const toolExecutionId = generateId(); // use our own id to guarantee uniqueness | ||
| outstandingToolResults.add(toolExecutionId); | ||
| // Note: we don't await the tool execution here (by leaving out 'await' on recordSpan), | ||
| // because we want to process the next chunk as soon as possible. | ||
| // This is important for the case where the tool execution takes a long time. | ||
| executeToolCall({ | ||
| toolCall, | ||
| tools, | ||
| tracer, | ||
| telemetry, | ||
| messages, | ||
| abortSignal, | ||
| experimental_context, | ||
| stepNumber, | ||
| model, | ||
| onToolCallStart, | ||
| onToolCallFinish, | ||
| onPreliminaryToolResult: result => { | ||
| enqueueToolResult(result); | ||
| }, | ||
| }) | ||
| .then(result => { | ||
| enqueueToolResult(result); | ||
| }) | ||
| .catch(error => { | ||
| enqueueToolResult({ | ||
| type: 'error', | ||
| error, | ||
| }); | ||
| }) | ||
| .finally(() => { | ||
| outstandingToolResults.delete(toolExecutionId); | ||
| attemptClose(); | ||
| }); | ||
| toolCallsToExecute.push(toolCall); | ||
| } | ||
@@ -426,0 +438,0 @@ } catch (error) { |
@@ -61,3 +61,3 @@ import { | ||
| part.state === 'approval-responded' || | ||
| part.state === 'output-available' || | ||
| (part.state === 'output-available' && part.preliminary !== true) || | ||
| part.state === 'output-error' || | ||
@@ -64,0 +64,0 @@ part.state === 'output-denied', |
@@ -11,7 +11,15 @@ import { createResolvablePromise } from './create-resolvable-promise'; | ||
| stream: ReadableStream<T>; | ||
| addStream: (innerStream: ReadableStream<T>) => void; | ||
| addStream: ( | ||
| innerStream: ReadableStream<T>, | ||
| callbacks?: { | ||
| onError?: (error: unknown) => void; | ||
| }, | ||
| ) => void; | ||
| close: () => void; | ||
| terminate: () => void; | ||
| } { | ||
| let innerStreamReaders: ReadableStreamDefaultReader<T>[] = []; | ||
| let innerStreams: Array<{ | ||
| reader: ReadableStreamDefaultReader<T>; | ||
| onError?: (error: unknown) => void; | ||
| }> = []; | ||
| let controller: ReadableStreamDefaultController<T> | null = null; | ||
@@ -25,4 +33,4 @@ let isClosed = false; | ||
| innerStreamReaders.forEach(reader => reader.cancel()); | ||
| innerStreamReaders = []; | ||
| innerStreams.forEach(({ reader }) => reader.cancel()); | ||
| innerStreams = []; | ||
| controller?.close(); | ||
@@ -33,3 +41,3 @@ }; | ||
| // Case 1: Outer stream is closed and no more inner streams | ||
| if (isClosed && innerStreamReaders.length === 0) { | ||
| if (isClosed && innerStreams.length === 0) { | ||
| controller?.close(); | ||
@@ -41,3 +49,3 @@ return; | ||
| // wait for a new inner stream to be added or the outer stream to close | ||
| if (innerStreamReaders.length === 0) { | ||
| if (innerStreams.length === 0) { | ||
| waitForNewStream = createResolvablePromise<void>(); | ||
@@ -49,9 +57,9 @@ await waitForNewStream.promise; | ||
| try { | ||
| const { value, done } = await innerStreamReaders[0].read(); | ||
| const { value, done } = await innerStreams[0].reader.read(); | ||
| if (done) { | ||
| // Case 3: Current inner stream is done | ||
| innerStreamReaders.shift(); // Remove the finished stream | ||
| innerStreams.shift(); // Remove the finished stream | ||
| if (innerStreamReaders.length === 0 && isClosed) { | ||
| if (innerStreams.length === 0 && isClosed) { | ||
| // when closed and no more inner streams, stop pulling | ||
@@ -69,4 +77,5 @@ controller?.close(); | ||
| // Case 5: Current inner stream throws an error | ||
| innerStreams[0].onError?.(error); | ||
| controller?.error(error); | ||
| innerStreamReaders.shift(); // Remove the errored stream | ||
| innerStreams.shift(); // Remove the errored stream | ||
| terminate(); // we have errored, terminate all streams | ||
@@ -83,10 +92,15 @@ } | ||
| async cancel() { | ||
| for (const reader of innerStreamReaders) { | ||
| for (const { reader } of innerStreams) { | ||
| await reader.cancel(); | ||
| } | ||
| innerStreamReaders = []; | ||
| innerStreams = []; | ||
| isClosed = true; | ||
| }, | ||
| }), | ||
| addStream: (innerStream: ReadableStream<T>) => { | ||
| addStream: ( | ||
| innerStream: ReadableStream<T>, | ||
| callbacks?: { | ||
| onError?: (error: unknown) => void; | ||
| }, | ||
| ) => { | ||
| if (isClosed) { | ||
@@ -96,3 +110,6 @@ throw new Error('Cannot add inner stream: outer stream is closed'); | ||
| innerStreamReaders.push(innerStream.getReader()); | ||
| innerStreams.push({ | ||
| reader: innerStream.getReader(), | ||
| ...callbacks, | ||
| }); | ||
| waitForNewStream.resolve(); | ||
@@ -109,3 +126,3 @@ }, | ||
| if (innerStreamReaders.length === 0) { | ||
| if (innerStreams.length === 0) { | ||
| controller?.close(); | ||
@@ -112,0 +129,0 @@ } |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
6851251
0.24%525
0.19%64848
0.35%