+2
-2
| { | ||
| "name": "ai", | ||
| "version": "7.0.55", | ||
| "version": "7.0.56", | ||
| "type": "module", | ||
@@ -45,3 +45,3 @@ "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.", | ||
| "dependencies": { | ||
| "@ai-sdk/gateway": "4.0.43", | ||
| "@ai-sdk/gateway": "4.0.44", | ||
| "@ai-sdk/provider": "4.0.6", | ||
@@ -48,0 +48,0 @@ "@ai-sdk/provider-utils": "5.0.23" |
| import type { ToolSet } from '@ai-sdk/provider-utils'; | ||
| import type { Callback } from '../util/callback'; | ||
| import type { FinishReason } from '../types/language-model'; | ||
| import type { ProviderMetadata } from '../types/provider-metadata'; | ||
| import type { LanguageModelUsage } from '../types/usage'; | ||
@@ -58,2 +59,5 @@ import type { ContentPart } from './content-part'; | ||
| /** Optional provider-specific metadata for this model call. */ | ||
| readonly providerMetadata?: ProviderMetadata; | ||
| /** Performance metrics for the model call. */ | ||
@@ -60,0 +64,0 @@ readonly performance: { |
@@ -599,2 +599,5 @@ import { | ||
| responseId, | ||
| ...(chunk.providerMetadata != null | ||
| ? { providerMetadata: chunk.providerMetadata } | ||
| : {}), | ||
| performance, | ||
@@ -601,0 +604,0 @@ }, |
@@ -14,2 +14,3 @@ import type { | ||
| delay as defaultDelay, | ||
| generateId, | ||
| withUserAgentSuffix, | ||
@@ -557,9 +558,19 @@ type DataContent, | ||
| // 2. Start the generation | ||
| const startResult = await retry(() => | ||
| model.doStart!({ | ||
| ...callOptions, | ||
| webhookUrl, | ||
| }), | ||
| // 2. Start the generation. `doStart` is billable: mint one idempotency token | ||
| // per logical start, outside the retry closure; a caller-supplied key wins. | ||
| const callerIdempotencyKey = Object.entries(callOptions.headers ?? {}).find( | ||
| ([key, value]) => | ||
| key.toLowerCase() === 'idempotency-key' && value !== undefined, | ||
| ); | ||
| const startCallOptions = { | ||
| ...callOptions, | ||
| headers: { | ||
| ...callOptions.headers, | ||
| ...(callerIdempotencyKey | ||
| ? {} | ||
| : { 'idempotency-key': `aisdk_vid_${generateId()}` }), | ||
| }, | ||
| webhookUrl, | ||
| }; | ||
| const startResult = await retry(() => model.doStart!(startCallOptions)); | ||
@@ -566,0 +577,0 @@ const allWarnings = [...earlyWarnings, ...startResult.warnings]; |
@@ -67,2 +67,3 @@ import type { UIMessageChunk } from '../ui-message-stream'; | ||
| * @param options.chatId - Unique identifier for the chat session to reconnect to | ||
| * @param options.abortSignal - Signal to abort the reconnection request if needed | ||
| * @param options.headers - Additional HTTP headers to include in the reconnection request | ||
@@ -82,4 +83,6 @@ * @param options.body - Additional JSON properties to include in the request body | ||
| chatId: string; | ||
| /** Signal to abort the reconnection request if needed */ | ||
| abortSignal?: AbortSignal; | ||
| } & ChatRequestOptions, | ||
| ) => Promise<ReadableStream<UIMessageChunk> | null>; | ||
| } |
+87
-12
@@ -138,2 +138,6 @@ import { | ||
| type ActiveResumeRequest = { | ||
| abortController: AbortController; | ||
| }; | ||
| export interface ChatState<UI_MESSAGE extends UIMessage> { | ||
@@ -258,2 +262,3 @@ status: ChatStatus; | ||
| private activeResponse: ActiveResponse<UI_MESSAGE> | undefined = undefined; | ||
| private activeResumeRequest: ActiveResumeRequest | undefined = undefined; | ||
| private jobExecutor = new SerialJobExecutor(); | ||
@@ -589,7 +594,4 @@ | ||
| stop = async () => { | ||
| if (this.status !== 'streaming' && this.status !== 'submitted') return; | ||
| if (this.activeResponse?.abortController) { | ||
| this.activeResponse.abortController.abort(); | ||
| } | ||
| this.activeResumeRequest?.abortController.abort(); | ||
| this.activeResponse?.abortController.abort(); | ||
| }; | ||
@@ -622,2 +624,21 @@ | ||
| } & ChatRequestOptions) { | ||
| const abortController = new AbortController(); | ||
| const activeResumeRequest = | ||
| trigger === 'resume-stream' ? { abortController } : undefined; | ||
| if (activeResumeRequest) { | ||
| this.activeResumeRequest?.abortController.abort(); | ||
| this.activeResumeRequest = activeResumeRequest; | ||
| } | ||
| const isCurrentRequest = () => | ||
| activeResumeRequest == null || | ||
| this.activeResumeRequest === activeResumeRequest; | ||
| const clearActiveResumeRequest = () => { | ||
| if (this.activeResumeRequest === activeResumeRequest) { | ||
| this.activeResumeRequest = undefined; | ||
| } | ||
| }; | ||
| // For resume-stream, check if there's an active stream before | ||
@@ -631,2 +652,3 @@ // changing status. This avoids a brief flash of 'submitted' status | ||
| chatId: this.id, | ||
| abortSignal: abortController.signal, | ||
| metadata, | ||
@@ -637,3 +659,14 @@ headers, | ||
| if (abortController.signal.aborted || !isCurrentRequest()) { | ||
| await reconnect?.cancel().catch(() => {}); | ||
| if (isCurrentRequest()) { | ||
| this.setStatus({ status: 'ready' }); | ||
| } | ||
| clearActiveResumeRequest(); | ||
| return; | ||
| } | ||
| if (reconnect == null) { | ||
| this.setStatus({ status: 'ready' }); | ||
| clearActiveResumeRequest(); | ||
| return; // no active stream found, so we do not resume | ||
@@ -644,2 +677,17 @@ } | ||
| } catch (err) { | ||
| if ( | ||
| abortController.signal.aborted || | ||
| (err as { name?: string }).name === 'AbortError' | ||
| ) { | ||
| if (isCurrentRequest()) { | ||
| this.setStatus({ status: 'ready' }); | ||
| } | ||
| clearActiveResumeRequest(); | ||
| return; | ||
| } | ||
| if (!isCurrentRequest()) { | ||
| return; | ||
| } | ||
| if (this.onError && err instanceof Error) { | ||
@@ -649,2 +697,3 @@ this.onError(err); | ||
| this.setStatus({ status: 'error', error: err as Error }); | ||
| clearActiveResumeRequest(); | ||
| return; | ||
@@ -672,3 +721,3 @@ } | ||
| }), | ||
| abortController: new AbortController(), | ||
| abortController, | ||
| } as ActiveResponse<UI_MESSAGE>; | ||
@@ -708,6 +757,14 @@ | ||
| // serialize the job execution to avoid race conditions: | ||
| this.jobExecutor.run(() => | ||
| job({ | ||
| this.jobExecutor.run(() => { | ||
| if (response.abortController.signal.aborted) { | ||
| return Promise.resolve(); | ||
| } | ||
| return job({ | ||
| state: response.state, | ||
| write: () => { | ||
| if (response.abortController.signal.aborted) { | ||
| return; | ||
| } | ||
| // streaming is set on first write (before it should be "submitted") | ||
@@ -728,4 +785,4 @@ this.setStatus({ status: 'streaming' }); | ||
| }, | ||
| }), | ||
| ); | ||
| }); | ||
| }); | ||
@@ -744,2 +801,3 @@ await consumeStream({ | ||
| }), | ||
| abortSignal: response.abortController.signal, | ||
| onError: error => { | ||
@@ -750,3 +808,12 @@ throw error; | ||
| this.setStatus({ status: 'ready' }); | ||
| if (isAbort) { | ||
| if (isCurrentRequest()) { | ||
| this.setStatus({ status: 'ready' }); | ||
| } | ||
| return null; | ||
| } | ||
| if (isCurrentRequest()) { | ||
| this.setStatus({ status: 'ready' }); | ||
| } | ||
| } catch (err) { | ||
@@ -756,6 +823,12 @@ // Ignore abort errors as they are expected. | ||
| isAbort = true; | ||
| this.setStatus({ status: 'ready' }); | ||
| if (isCurrentRequest()) { | ||
| this.setStatus({ status: 'ready' }); | ||
| } | ||
| return null; | ||
| } | ||
| if (!isCurrentRequest()) { | ||
| return null; | ||
| } | ||
| isError = true; | ||
@@ -796,2 +869,4 @@ | ||
| } | ||
| clearActiveResumeRequest(); | ||
| } | ||
@@ -798,0 +873,0 @@ |
@@ -250,2 +250,3 @@ import { | ||
| credentials, | ||
| signal: options.abortSignal, | ||
| }); | ||
@@ -252,0 +253,0 @@ |
@@ -16,7 +16,19 @@ /** | ||
| onError, | ||
| abortSignal, | ||
| }: { | ||
| stream: ReadableStream; | ||
| onError?: (error: unknown) => void; | ||
| abortSignal?: AbortSignal; | ||
| }): Promise<void> { | ||
| const reader = stream.getReader(); | ||
| const cancelOnAbort = () => { | ||
| reader.cancel().catch(() => {}); | ||
| }; | ||
| if (abortSignal?.aborted) { | ||
| cancelOnAbort(); | ||
| } else { | ||
| abortSignal?.addEventListener('abort', cancelOnAbort, { once: true }); | ||
| } | ||
| try { | ||
@@ -30,4 +42,5 @@ while (true) { | ||
| } finally { | ||
| abortSignal?.removeEventListener('abort', cancelOnAbort); | ||
| reader.releaseLock(); | ||
| } | ||
| } |
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
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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.
6667470
0.21%69218
0.27%+ Added
- Removed
Updated