ai 6.0.32 → 6.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +12 -2
- package/dist/index.mjs.map +1 -1
- package/dist/internal/index.js +1 -1
- package/dist/internal/index.mjs +1 -1
- package/docs/02-foundations/03-prompts.mdx +2 -2
- package/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/28-output.mdx +1 -1
- package/package.json +6 -4
- package/src/agent/agent.ts +116 -0
- package/src/agent/create-agent-ui-stream-response.test.ts +258 -0
- package/src/agent/create-agent-ui-stream-response.ts +50 -0
- package/src/agent/create-agent-ui-stream.ts +73 -0
- package/src/agent/index.ts +33 -0
- package/src/agent/infer-agent-tools.ts +7 -0
- package/src/agent/infer-agent-ui-message.test-d.ts +54 -0
- package/src/agent/infer-agent-ui-message.ts +11 -0
- package/src/agent/pipe-agent-ui-stream-to-response.ts +52 -0
- package/src/agent/tool-loop-agent-on-finish-callback.ts +31 -0
- package/src/agent/tool-loop-agent-on-step-finish-callback.ts +11 -0
- package/src/agent/tool-loop-agent-settings.ts +182 -0
- package/src/agent/tool-loop-agent.test-d.ts +114 -0
- package/src/agent/tool-loop-agent.test.ts +442 -0
- package/src/agent/tool-loop-agent.ts +114 -0
- package/src/embed/__snapshots__/embed-many.test.ts.snap +191 -0
- package/src/embed/__snapshots__/embed.test.ts.snap +81 -0
- package/src/embed/embed-many-result.ts +53 -0
- package/src/embed/embed-many.test.ts +653 -0
- package/src/embed/embed-many.ts +378 -0
- package/src/embed/embed-result.ts +50 -0
- package/src/embed/embed.test.ts +298 -0
- package/src/embed/embed.ts +211 -0
- package/src/embed/index.ts +4 -0
- package/src/error/index.ts +34 -0
- package/src/error/invalid-argument-error.ts +34 -0
- package/src/error/invalid-stream-part-error.ts +28 -0
- package/src/error/invalid-tool-approval-error.ts +26 -0
- package/src/error/invalid-tool-input-error.ts +33 -0
- package/src/error/no-image-generated-error.ts +39 -0
- package/src/error/no-object-generated-error.ts +70 -0
- package/src/error/no-output-generated-error.ts +26 -0
- package/src/error/no-speech-generated-error.ts +18 -0
- package/src/error/no-such-tool-error.ts +35 -0
- package/src/error/no-transcript-generated-error.ts +20 -0
- package/src/error/tool-call-not-found-for-approval-error.ts +32 -0
- package/src/error/tool-call-repair-error.ts +30 -0
- package/src/error/unsupported-model-version-error.ts +23 -0
- package/src/error/verify-no-object-generated-error.ts +27 -0
- package/src/generate-image/generate-image-result.ts +42 -0
- package/src/generate-image/generate-image.test.ts +1420 -0
- package/src/generate-image/generate-image.ts +360 -0
- package/src/generate-image/index.ts +18 -0
- package/src/generate-object/__snapshots__/generate-object.test.ts.snap +133 -0
- package/src/generate-object/__snapshots__/stream-object.test.ts.snap +297 -0
- package/src/generate-object/generate-object-result.ts +67 -0
- package/src/generate-object/generate-object.test-d.ts +49 -0
- package/src/generate-object/generate-object.test.ts +1191 -0
- package/src/generate-object/generate-object.ts +518 -0
- package/src/generate-object/index.ts +9 -0
- package/src/generate-object/inject-json-instruction.test.ts +181 -0
- package/src/generate-object/inject-json-instruction.ts +30 -0
- package/src/generate-object/output-strategy.ts +415 -0
- package/src/generate-object/parse-and-validate-object-result.ts +111 -0
- package/src/generate-object/repair-text.ts +12 -0
- package/src/generate-object/stream-object-result.ts +120 -0
- package/src/generate-object/stream-object.test-d.ts +74 -0
- package/src/generate-object/stream-object.test.ts +1950 -0
- package/src/generate-object/stream-object.ts +986 -0
- package/src/generate-object/validate-object-generation-input.ts +144 -0
- package/src/generate-speech/generate-speech-result.ts +30 -0
- package/src/generate-speech/generate-speech.test.ts +300 -0
- package/src/generate-speech/generate-speech.ts +190 -0
- package/src/generate-speech/generated-audio-file.ts +65 -0
- package/src/generate-speech/index.ts +3 -0
- package/src/generate-text/__snapshots__/generate-text.test.ts.snap +1872 -0
- package/src/generate-text/__snapshots__/stream-text.test.ts.snap +1255 -0
- package/src/generate-text/collect-tool-approvals.test.ts +553 -0
- package/src/generate-text/collect-tool-approvals.ts +116 -0
- package/src/generate-text/content-part.ts +25 -0
- package/src/generate-text/execute-tool-call.ts +129 -0
- package/src/generate-text/extract-reasoning-content.ts +17 -0
- package/src/generate-text/extract-text-content.ts +15 -0
- package/src/generate-text/generate-text-result.ts +168 -0
- package/src/generate-text/generate-text.test-d.ts +68 -0
- package/src/generate-text/generate-text.test.ts +7011 -0
- package/src/generate-text/generate-text.ts +1223 -0
- package/src/generate-text/generated-file.ts +70 -0
- package/src/generate-text/index.ts +57 -0
- package/src/generate-text/is-approval-needed.ts +29 -0
- package/src/generate-text/output-utils.ts +23 -0
- package/src/generate-text/output.test.ts +698 -0
- package/src/generate-text/output.ts +590 -0
- package/src/generate-text/parse-tool-call.test.ts +570 -0
- package/src/generate-text/parse-tool-call.ts +188 -0
- package/src/generate-text/prepare-step.ts +103 -0
- package/src/generate-text/prune-messages.test.ts +720 -0
- package/src/generate-text/prune-messages.ts +167 -0
- package/src/generate-text/reasoning-output.ts +20 -0
- package/src/generate-text/reasoning.ts +8 -0
- package/src/generate-text/response-message.ts +10 -0
- package/src/generate-text/run-tools-transformation.test.ts +1143 -0
- package/src/generate-text/run-tools-transformation.ts +420 -0
- package/src/generate-text/smooth-stream.test.ts +2101 -0
- package/src/generate-text/smooth-stream.ts +162 -0
- package/src/generate-text/step-result.ts +238 -0
- package/src/generate-text/stop-condition.ts +29 -0
- package/src/generate-text/stream-text-result.ts +463 -0
- package/src/generate-text/stream-text.test-d.ts +200 -0
- package/src/generate-text/stream-text.test.ts +19979 -0
- package/src/generate-text/stream-text.ts +2505 -0
- package/src/generate-text/to-response-messages.test.ts +922 -0
- package/src/generate-text/to-response-messages.ts +163 -0
- package/src/generate-text/tool-approval-request-output.ts +21 -0
- package/src/generate-text/tool-call-repair-function.ts +27 -0
- package/src/generate-text/tool-call.ts +47 -0
- package/src/generate-text/tool-error.ts +34 -0
- package/src/generate-text/tool-output-denied.ts +21 -0
- package/src/generate-text/tool-output.ts +7 -0
- package/src/generate-text/tool-result.ts +36 -0
- package/src/generate-text/tool-set.ts +14 -0
- package/src/global.ts +24 -0
- package/src/index.ts +50 -0
- package/src/logger/index.ts +6 -0
- package/src/logger/log-warnings.test.ts +351 -0
- package/src/logger/log-warnings.ts +119 -0
- package/src/middleware/__snapshots__/simulate-streaming-middleware.test.ts.snap +64 -0
- package/src/middleware/add-tool-input-examples-middleware.test.ts +476 -0
- package/src/middleware/add-tool-input-examples-middleware.ts +90 -0
- package/src/middleware/default-embedding-settings-middleware.test.ts +126 -0
- package/src/middleware/default-embedding-settings-middleware.ts +22 -0
- package/src/middleware/default-settings-middleware.test.ts +388 -0
- package/src/middleware/default-settings-middleware.ts +33 -0
- package/src/middleware/extract-json-middleware.test.ts +827 -0
- package/src/middleware/extract-json-middleware.ts +197 -0
- package/src/middleware/extract-reasoning-middleware.test.ts +1028 -0
- package/src/middleware/extract-reasoning-middleware.ts +238 -0
- package/src/middleware/index.ts +10 -0
- package/src/middleware/simulate-streaming-middleware.test.ts +911 -0
- package/src/middleware/simulate-streaming-middleware.ts +79 -0
- package/src/middleware/wrap-embedding-model.test.ts +358 -0
- package/src/middleware/wrap-embedding-model.ts +86 -0
- package/src/middleware/wrap-image-model.test.ts +423 -0
- package/src/middleware/wrap-image-model.ts +85 -0
- package/src/middleware/wrap-language-model.test.ts +518 -0
- package/src/middleware/wrap-language-model.ts +104 -0
- package/src/middleware/wrap-provider.test.ts +120 -0
- package/src/middleware/wrap-provider.ts +51 -0
- package/src/model/as-embedding-model-v3.test.ts +319 -0
- package/src/model/as-embedding-model-v3.ts +24 -0
- package/src/model/as-image-model-v3.test.ts +409 -0
- package/src/model/as-image-model-v3.ts +24 -0
- package/src/model/as-language-model-v3.test.ts +508 -0
- package/src/model/as-language-model-v3.ts +103 -0
- package/src/model/as-provider-v3.ts +36 -0
- package/src/model/as-speech-model-v3.test.ts +356 -0
- package/src/model/as-speech-model-v3.ts +24 -0
- package/src/model/as-transcription-model-v3.test.ts +529 -0
- package/src/model/as-transcription-model-v3.ts +24 -0
- package/src/model/resolve-model.test.ts +244 -0
- package/src/model/resolve-model.ts +126 -0
- package/src/prompt/call-settings.ts +148 -0
- package/src/prompt/content-part.ts +209 -0
- package/src/prompt/convert-to-language-model-prompt.test.ts +2018 -0
- package/src/prompt/convert-to-language-model-prompt.ts +442 -0
- package/src/prompt/create-tool-model-output.test.ts +508 -0
- package/src/prompt/create-tool-model-output.ts +34 -0
- package/src/prompt/data-content.test.ts +15 -0
- package/src/prompt/data-content.ts +134 -0
- package/src/prompt/index.ts +27 -0
- package/src/prompt/invalid-data-content-error.ts +29 -0
- package/src/prompt/invalid-message-role-error.ts +27 -0
- package/src/prompt/message-conversion-error.ts +28 -0
- package/src/prompt/message.ts +68 -0
- package/src/prompt/prepare-call-settings.test.ts +159 -0
- package/src/prompt/prepare-call-settings.ts +108 -0
- package/src/prompt/prepare-tools-and-tool-choice.test.ts +461 -0
- package/src/prompt/prepare-tools-and-tool-choice.ts +86 -0
- package/src/prompt/prompt.ts +43 -0
- package/src/prompt/split-data-url.ts +17 -0
- package/src/prompt/standardize-prompt.test.ts +82 -0
- package/src/prompt/standardize-prompt.ts +99 -0
- package/src/prompt/wrap-gateway-error.ts +29 -0
- package/src/registry/custom-provider.test.ts +211 -0
- package/src/registry/custom-provider.ts +155 -0
- package/src/registry/index.ts +7 -0
- package/src/registry/no-such-provider-error.ts +41 -0
- package/src/registry/provider-registry.test.ts +691 -0
- package/src/registry/provider-registry.ts +328 -0
- package/src/rerank/index.ts +2 -0
- package/src/rerank/rerank-result.ts +70 -0
- package/src/rerank/rerank.test.ts +516 -0
- package/src/rerank/rerank.ts +237 -0
- package/src/telemetry/assemble-operation-name.ts +21 -0
- package/src/telemetry/get-base-telemetry-attributes.ts +53 -0
- package/src/telemetry/get-tracer.ts +20 -0
- package/src/telemetry/noop-tracer.ts +69 -0
- package/src/telemetry/record-span.ts +63 -0
- package/src/telemetry/select-telemetry-attributes.ts +78 -0
- package/src/telemetry/select-temetry-attributes.test.ts +114 -0
- package/src/telemetry/stringify-for-telemetry.test.ts +114 -0
- package/src/telemetry/stringify-for-telemetry.ts +33 -0
- package/src/telemetry/telemetry-settings.ts +44 -0
- package/src/test/mock-embedding-model-v2.ts +35 -0
- package/src/test/mock-embedding-model-v3.ts +48 -0
- package/src/test/mock-image-model-v2.ts +28 -0
- package/src/test/mock-image-model-v3.ts +28 -0
- package/src/test/mock-language-model-v2.ts +72 -0
- package/src/test/mock-language-model-v3.ts +77 -0
- package/src/test/mock-provider-v2.ts +68 -0
- package/src/test/mock-provider-v3.ts +80 -0
- package/src/test/mock-reranking-model-v3.ts +25 -0
- package/src/test/mock-server-response.ts +69 -0
- package/src/test/mock-speech-model-v2.ts +24 -0
- package/src/test/mock-speech-model-v3.ts +24 -0
- package/src/test/mock-tracer.ts +156 -0
- package/src/test/mock-transcription-model-v2.ts +24 -0
- package/src/test/mock-transcription-model-v3.ts +24 -0
- package/src/test/mock-values.ts +4 -0
- package/src/test/not-implemented.ts +3 -0
- package/src/text-stream/create-text-stream-response.test.ts +38 -0
- package/src/text-stream/create-text-stream-response.ts +18 -0
- package/src/text-stream/index.ts +2 -0
- package/src/text-stream/pipe-text-stream-to-response.test.ts +38 -0
- package/src/text-stream/pipe-text-stream-to-response.ts +26 -0
- package/src/transcribe/index.ts +2 -0
- package/src/transcribe/transcribe-result.ts +60 -0
- package/src/transcribe/transcribe.test.ts +313 -0
- package/src/transcribe/transcribe.ts +173 -0
- package/src/types/embedding-model-middleware.ts +3 -0
- package/src/types/embedding-model.ts +18 -0
- package/src/types/image-model-middleware.ts +3 -0
- package/src/types/image-model-response-metadata.ts +16 -0
- package/src/types/image-model.ts +19 -0
- package/src/types/index.ts +29 -0
- package/src/types/json-value.ts +15 -0
- package/src/types/language-model-middleware.ts +3 -0
- package/src/types/language-model-request-metadata.ts +6 -0
- package/src/types/language-model-response-metadata.ts +21 -0
- package/src/types/language-model.ts +104 -0
- package/src/types/provider-metadata.ts +16 -0
- package/src/types/provider.ts +55 -0
- package/src/types/reranking-model.ts +6 -0
- package/src/types/speech-model-response-metadata.ts +21 -0
- package/src/types/speech-model.ts +6 -0
- package/src/types/transcription-model-response-metadata.ts +16 -0
- package/src/types/transcription-model.ts +9 -0
- package/src/types/usage.ts +200 -0
- package/src/types/warning.ts +7 -0
- package/src/ui/__snapshots__/append-response-messages.test.ts.snap +416 -0
- package/src/ui/__snapshots__/convert-to-model-messages.test.ts.snap +419 -0
- package/src/ui/__snapshots__/process-chat-text-response.test.ts.snap +142 -0
- package/src/ui/call-completion-api.ts +157 -0
- package/src/ui/chat-transport.ts +83 -0
- package/src/ui/chat.test-d.ts +233 -0
- package/src/ui/chat.test.ts +2695 -0
- package/src/ui/chat.ts +716 -0
- package/src/ui/convert-file-list-to-file-ui-parts.ts +36 -0
- package/src/ui/convert-to-model-messages.test.ts +2775 -0
- package/src/ui/convert-to-model-messages.ts +373 -0
- package/src/ui/default-chat-transport.ts +36 -0
- package/src/ui/direct-chat-transport.test.ts +446 -0
- package/src/ui/direct-chat-transport.ts +118 -0
- package/src/ui/http-chat-transport.test.ts +185 -0
- package/src/ui/http-chat-transport.ts +292 -0
- package/src/ui/index.ts +71 -0
- package/src/ui/last-assistant-message-is-complete-with-approval-responses.ts +44 -0
- package/src/ui/last-assistant-message-is-complete-with-tool-calls.test.ts +371 -0
- package/src/ui/last-assistant-message-is-complete-with-tool-calls.ts +39 -0
- package/src/ui/process-text-stream.test.ts +38 -0
- package/src/ui/process-text-stream.ts +16 -0
- package/src/ui/process-ui-message-stream.test.ts +8052 -0
- package/src/ui/process-ui-message-stream.ts +713 -0
- package/src/ui/text-stream-chat-transport.ts +23 -0
- package/src/ui/transform-text-to-ui-message-stream.test.ts +124 -0
- package/src/ui/transform-text-to-ui-message-stream.ts +27 -0
- package/src/ui/ui-messages.test.ts +48 -0
- package/src/ui/ui-messages.ts +534 -0
- package/src/ui/use-completion.ts +84 -0
- package/src/ui/validate-ui-messages.test.ts +1428 -0
- package/src/ui/validate-ui-messages.ts +476 -0
- package/src/ui-message-stream/create-ui-message-stream-response.test.ts +266 -0
- package/src/ui-message-stream/create-ui-message-stream-response.ts +32 -0
- package/src/ui-message-stream/create-ui-message-stream.test.ts +639 -0
- package/src/ui-message-stream/create-ui-message-stream.ts +124 -0
- package/src/ui-message-stream/get-response-ui-message-id.test.ts +55 -0
- package/src/ui-message-stream/get-response-ui-message-id.ts +24 -0
- package/src/ui-message-stream/handle-ui-message-stream-finish.test.ts +429 -0
- package/src/ui-message-stream/handle-ui-message-stream-finish.ts +135 -0
- package/src/ui-message-stream/index.ts +13 -0
- package/src/ui-message-stream/json-to-sse-transform-stream.ts +12 -0
- package/src/ui-message-stream/pipe-ui-message-stream-to-response.test.ts +90 -0
- package/src/ui-message-stream/pipe-ui-message-stream-to-response.ts +40 -0
- package/src/ui-message-stream/read-ui-message-stream.test.ts +122 -0
- package/src/ui-message-stream/read-ui-message-stream.ts +87 -0
- package/src/ui-message-stream/ui-message-chunks.test-d.ts +18 -0
- package/src/ui-message-stream/ui-message-chunks.ts +344 -0
- package/src/ui-message-stream/ui-message-stream-headers.ts +7 -0
- package/src/ui-message-stream/ui-message-stream-on-finish-callback.ts +32 -0
- package/src/ui-message-stream/ui-message-stream-response-init.ts +5 -0
- package/src/ui-message-stream/ui-message-stream-writer.ts +24 -0
- package/src/util/as-array.ts +3 -0
- package/src/util/async-iterable-stream.test.ts +241 -0
- package/src/util/async-iterable-stream.ts +94 -0
- package/src/util/consume-stream.ts +29 -0
- package/src/util/cosine-similarity.test.ts +57 -0
- package/src/util/cosine-similarity.ts +47 -0
- package/src/util/create-resolvable-promise.ts +30 -0
- package/src/util/create-stitchable-stream.test.ts +239 -0
- package/src/util/create-stitchable-stream.ts +112 -0
- package/src/util/data-url.ts +17 -0
- package/src/util/deep-partial.ts +84 -0
- package/src/util/detect-media-type.test.ts +670 -0
- package/src/util/detect-media-type.ts +184 -0
- package/src/util/download/download-function.ts +45 -0
- package/src/util/download/download.test.ts +69 -0
- package/src/util/download/download.ts +46 -0
- package/src/util/error-handler.ts +1 -0
- package/src/util/fix-json.test.ts +279 -0
- package/src/util/fix-json.ts +401 -0
- package/src/util/get-potential-start-index.test.ts +34 -0
- package/src/util/get-potential-start-index.ts +30 -0
- package/src/util/index.ts +11 -0
- package/src/util/is-deep-equal-data.test.ts +119 -0
- package/src/util/is-deep-equal-data.ts +48 -0
- package/src/util/is-non-empty-object.ts +5 -0
- package/src/util/job.ts +1 -0
- package/src/util/log-v2-compatibility-warning.ts +21 -0
- package/src/util/merge-abort-signals.test.ts +155 -0
- package/src/util/merge-abort-signals.ts +43 -0
- package/src/util/merge-objects.test.ts +118 -0
- package/src/util/merge-objects.ts +79 -0
- package/src/util/now.ts +4 -0
- package/src/util/parse-partial-json.test.ts +80 -0
- package/src/util/parse-partial-json.ts +30 -0
- package/src/util/prepare-headers.test.ts +51 -0
- package/src/util/prepare-headers.ts +14 -0
- package/src/util/prepare-retries.test.ts +10 -0
- package/src/util/prepare-retries.ts +47 -0
- package/src/util/retry-error.ts +41 -0
- package/src/util/retry-with-exponential-backoff.test.ts +446 -0
- package/src/util/retry-with-exponential-backoff.ts +154 -0
- package/src/util/serial-job-executor.test.ts +162 -0
- package/src/util/serial-job-executor.ts +36 -0
- package/src/util/simulate-readable-stream.test.ts +98 -0
- package/src/util/simulate-readable-stream.ts +39 -0
- package/src/util/split-array.test.ts +60 -0
- package/src/util/split-array.ts +20 -0
- package/src/util/value-of.ts +65 -0
- package/src/util/write-to-server-response.test.ts +266 -0
- package/src/util/write-to-server-response.ts +49 -0
- package/src/version.ts +5 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateId as generateIdFunc,
|
|
3
|
+
getErrorMessage,
|
|
4
|
+
IdGenerator,
|
|
5
|
+
} from '@ai-sdk/provider-utils';
|
|
6
|
+
import { UIMessage } from '../ui/ui-messages';
|
|
7
|
+
import { handleUIMessageStreamFinish } from './handle-ui-message-stream-finish';
|
|
8
|
+
import { InferUIMessageChunk } from './ui-message-chunks';
|
|
9
|
+
import { UIMessageStreamOnFinishCallback } from './ui-message-stream-on-finish-callback';
|
|
10
|
+
import { UIMessageStreamWriter } from './ui-message-stream-writer';
|
|
11
|
+
|
|
12
|
+
export function createUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
13
|
+
execute,
|
|
14
|
+
onError = getErrorMessage,
|
|
15
|
+
originalMessages,
|
|
16
|
+
onFinish,
|
|
17
|
+
generateId = generateIdFunc,
|
|
18
|
+
}: {
|
|
19
|
+
execute: (options: {
|
|
20
|
+
writer: UIMessageStreamWriter<UI_MESSAGE>;
|
|
21
|
+
}) => Promise<void> | void;
|
|
22
|
+
onError?: (error: unknown) => string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The original messages. If they are provided, persistence mode is assumed,
|
|
26
|
+
* and a message ID is provided for the response message.
|
|
27
|
+
*/
|
|
28
|
+
originalMessages?: UI_MESSAGE[];
|
|
29
|
+
|
|
30
|
+
onFinish?: UIMessageStreamOnFinishCallback<UI_MESSAGE>;
|
|
31
|
+
|
|
32
|
+
generateId?: IdGenerator;
|
|
33
|
+
}): ReadableStream<InferUIMessageChunk<UI_MESSAGE>> {
|
|
34
|
+
let controller!: ReadableStreamDefaultController<
|
|
35
|
+
InferUIMessageChunk<UI_MESSAGE>
|
|
36
|
+
>;
|
|
37
|
+
|
|
38
|
+
const ongoingStreamPromises: Promise<void>[] = [];
|
|
39
|
+
|
|
40
|
+
const stream = new ReadableStream({
|
|
41
|
+
start(controllerArg) {
|
|
42
|
+
controller = controllerArg;
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
function safeEnqueue(data: InferUIMessageChunk<UI_MESSAGE>) {
|
|
47
|
+
try {
|
|
48
|
+
controller.enqueue(data);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
// suppress errors when the stream has been closed
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const result = execute({
|
|
56
|
+
writer: {
|
|
57
|
+
write(part: InferUIMessageChunk<UI_MESSAGE>) {
|
|
58
|
+
safeEnqueue(part);
|
|
59
|
+
},
|
|
60
|
+
merge(streamArg) {
|
|
61
|
+
ongoingStreamPromises.push(
|
|
62
|
+
(async () => {
|
|
63
|
+
const reader = streamArg.getReader();
|
|
64
|
+
while (true) {
|
|
65
|
+
const { done, value } = await reader.read();
|
|
66
|
+
if (done) break;
|
|
67
|
+
safeEnqueue(value);
|
|
68
|
+
}
|
|
69
|
+
})().catch(error => {
|
|
70
|
+
safeEnqueue({
|
|
71
|
+
type: 'error',
|
|
72
|
+
errorText: onError(error),
|
|
73
|
+
} as InferUIMessageChunk<UI_MESSAGE>);
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
},
|
|
77
|
+
onError,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (result) {
|
|
82
|
+
ongoingStreamPromises.push(
|
|
83
|
+
result.catch(error => {
|
|
84
|
+
safeEnqueue({
|
|
85
|
+
type: 'error',
|
|
86
|
+
errorText: onError(error),
|
|
87
|
+
} as InferUIMessageChunk<UI_MESSAGE>);
|
|
88
|
+
}),
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
} catch (error) {
|
|
92
|
+
safeEnqueue({
|
|
93
|
+
type: 'error',
|
|
94
|
+
errorText: onError(error),
|
|
95
|
+
} as InferUIMessageChunk<UI_MESSAGE>);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Wait until all ongoing streams are done. This approach enables merging
|
|
99
|
+
// streams even after execute has returned, as long as there is still an
|
|
100
|
+
// open merged stream. This is important to e.g. forward new streams and
|
|
101
|
+
// from callbacks.
|
|
102
|
+
const waitForStreams: Promise<void> = new Promise(async resolve => {
|
|
103
|
+
while (ongoingStreamPromises.length > 0) {
|
|
104
|
+
await ongoingStreamPromises.shift();
|
|
105
|
+
}
|
|
106
|
+
resolve();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
waitForStreams.finally(() => {
|
|
110
|
+
try {
|
|
111
|
+
controller.close();
|
|
112
|
+
} catch (error) {
|
|
113
|
+
// suppress errors when the stream has been closed
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return handleUIMessageStreamFinish<UI_MESSAGE>({
|
|
118
|
+
stream,
|
|
119
|
+
messageId: generateId(),
|
|
120
|
+
originalMessages,
|
|
121
|
+
onFinish,
|
|
122
|
+
onError,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { getResponseUIMessageId } from './get-response-ui-message-id';
|
|
3
|
+
import { UIMessage } from '../ui/ui-messages';
|
|
4
|
+
|
|
5
|
+
describe('getResponseUIMessageId', () => {
|
|
6
|
+
const mockGenerateId = () => 'new-id';
|
|
7
|
+
|
|
8
|
+
it('should return undefined when originalMessages is null', () => {
|
|
9
|
+
const result = getResponseUIMessageId({
|
|
10
|
+
originalMessages: undefined,
|
|
11
|
+
responseMessageId: mockGenerateId,
|
|
12
|
+
});
|
|
13
|
+
expect(result).toBeUndefined();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should return the last assistant message id when present', () => {
|
|
17
|
+
const messages: UIMessage[] = [
|
|
18
|
+
{ id: 'msg-1', role: 'user', parts: [] },
|
|
19
|
+
{ id: 'msg-2', role: 'assistant', parts: [] },
|
|
20
|
+
];
|
|
21
|
+
const result = getResponseUIMessageId({
|
|
22
|
+
originalMessages: messages,
|
|
23
|
+
responseMessageId: mockGenerateId,
|
|
24
|
+
});
|
|
25
|
+
expect(result).toBe('msg-2');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should generate new id when last message is not from assistant', () => {
|
|
29
|
+
const messages: UIMessage[] = [
|
|
30
|
+
{ id: 'msg-1', role: 'assistant', parts: [] },
|
|
31
|
+
{ id: 'msg-2', role: 'user', parts: [] },
|
|
32
|
+
];
|
|
33
|
+
const result = getResponseUIMessageId({
|
|
34
|
+
originalMessages: messages,
|
|
35
|
+
responseMessageId: mockGenerateId,
|
|
36
|
+
});
|
|
37
|
+
expect(result).toBe('new-id');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should generate new id when messages array is empty', () => {
|
|
41
|
+
const result = getResponseUIMessageId({
|
|
42
|
+
originalMessages: [],
|
|
43
|
+
responseMessageId: mockGenerateId,
|
|
44
|
+
});
|
|
45
|
+
expect(result).toBe('new-id');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should use the responseMessageId when it is a string', () => {
|
|
49
|
+
const result = getResponseUIMessageId({
|
|
50
|
+
originalMessages: [],
|
|
51
|
+
responseMessageId: 'response-id',
|
|
52
|
+
});
|
|
53
|
+
expect(result).toBe('response-id');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { IdGenerator } from '@ai-sdk/provider-utils';
|
|
2
|
+
import { UIMessage } from '../ui/ui-messages';
|
|
3
|
+
|
|
4
|
+
export function getResponseUIMessageId({
|
|
5
|
+
originalMessages,
|
|
6
|
+
responseMessageId,
|
|
7
|
+
}: {
|
|
8
|
+
originalMessages: UIMessage[] | undefined;
|
|
9
|
+
responseMessageId: string | IdGenerator;
|
|
10
|
+
}) {
|
|
11
|
+
// when there are no original messages (i.e. no persistence),
|
|
12
|
+
// the assistant message id generation is handled on the client side.
|
|
13
|
+
if (originalMessages == null) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const lastMessage = originalMessages[originalMessages.length - 1];
|
|
18
|
+
|
|
19
|
+
return lastMessage?.role === 'assistant'
|
|
20
|
+
? lastMessage.id
|
|
21
|
+
: typeof responseMessageId === 'function'
|
|
22
|
+
? responseMessageId()
|
|
23
|
+
: responseMessageId;
|
|
24
|
+
}
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import {
|
|
2
|
+
convertArrayToReadableStream,
|
|
3
|
+
convertReadableStreamToArray,
|
|
4
|
+
} from '@ai-sdk/provider-utils/test';
|
|
5
|
+
import { UIMessage } from '../ui/ui-messages';
|
|
6
|
+
import { handleUIMessageStreamFinish } from './handle-ui-message-stream-finish';
|
|
7
|
+
import { UIMessageChunk } from './ui-message-chunks';
|
|
8
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
9
|
+
|
|
10
|
+
function createUIMessageStream(parts: UIMessageChunk[]) {
|
|
11
|
+
return convertArrayToReadableStream(parts);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('handleUIMessageStreamFinish', () => {
|
|
15
|
+
const mockErrorHandler = vi.fn();
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
mockErrorHandler.mockClear();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('stream pass-through without onFinish', () => {
|
|
22
|
+
it('should pass through stream chunks without processing when onFinish is not provided', async () => {
|
|
23
|
+
const inputChunks: UIMessageChunk[] = [
|
|
24
|
+
{ type: 'start', messageId: 'msg-123' },
|
|
25
|
+
{ type: 'text-start', id: 'text-1' },
|
|
26
|
+
{ type: 'text-delta', id: 'text-1', delta: 'Hello' },
|
|
27
|
+
{ type: 'text-delta', id: 'text-1', delta: ' World' },
|
|
28
|
+
{ type: 'text-end', id: 'text-1' },
|
|
29
|
+
{ type: 'finish' },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const stream = createUIMessageStream(inputChunks);
|
|
33
|
+
|
|
34
|
+
const resultStream = handleUIMessageStreamFinish<UIMessage>({
|
|
35
|
+
stream,
|
|
36
|
+
messageId: 'msg-123',
|
|
37
|
+
originalMessages: [],
|
|
38
|
+
onError: mockErrorHandler,
|
|
39
|
+
// onFinish is not provided
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const result = await convertReadableStreamToArray(resultStream);
|
|
43
|
+
|
|
44
|
+
expect(result).toEqual(inputChunks);
|
|
45
|
+
expect(mockErrorHandler).not.toHaveBeenCalled();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should inject messageId when not present in start chunk', async () => {
|
|
49
|
+
const inputChunks: UIMessageChunk[] = [
|
|
50
|
+
{ type: 'start' }, // no messageId
|
|
51
|
+
{ type: 'text-start', id: 'text-1' },
|
|
52
|
+
{ type: 'text-delta', id: 'text-1', delta: 'Test' },
|
|
53
|
+
{ type: 'text-end', id: 'text-1' },
|
|
54
|
+
{ type: 'finish' },
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const stream = createUIMessageStream(inputChunks);
|
|
58
|
+
|
|
59
|
+
const resultStream = handleUIMessageStreamFinish<UIMessage>({
|
|
60
|
+
stream,
|
|
61
|
+
messageId: 'injected-123',
|
|
62
|
+
originalMessages: [],
|
|
63
|
+
onError: mockErrorHandler,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const result = await convertReadableStreamToArray(resultStream);
|
|
67
|
+
|
|
68
|
+
expect(result[0]).toEqual({ type: 'start', messageId: 'injected-123' });
|
|
69
|
+
expect(result.slice(1)).toEqual(inputChunks.slice(1));
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('stream processing with onFinish callback', () => {
|
|
74
|
+
it('should process stream and call onFinish with correct parameters', async () => {
|
|
75
|
+
const onFinishCallback = vi.fn();
|
|
76
|
+
const inputChunks: UIMessageChunk[] = [
|
|
77
|
+
{ type: 'start', messageId: 'msg-456' },
|
|
78
|
+
{ type: 'text-start', id: 'text-1' },
|
|
79
|
+
{ type: 'text-delta', id: 'text-1', delta: 'Hello' },
|
|
80
|
+
{ type: 'text-delta', id: 'text-1', delta: ' World' },
|
|
81
|
+
{ type: 'text-end', id: 'text-1' },
|
|
82
|
+
{ type: 'finish' },
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
const originalMessages: UIMessage[] = [
|
|
86
|
+
{
|
|
87
|
+
id: 'user-msg-1',
|
|
88
|
+
role: 'user',
|
|
89
|
+
parts: [{ type: 'text', text: 'Hello' }],
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
const stream = createUIMessageStream(inputChunks);
|
|
94
|
+
|
|
95
|
+
const resultStream = handleUIMessageStreamFinish<UIMessage>({
|
|
96
|
+
stream,
|
|
97
|
+
messageId: 'msg-456',
|
|
98
|
+
originalMessages,
|
|
99
|
+
onError: mockErrorHandler,
|
|
100
|
+
onFinish: onFinishCallback,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const result = await convertReadableStreamToArray(resultStream);
|
|
104
|
+
|
|
105
|
+
expect(result).toEqual(inputChunks);
|
|
106
|
+
expect(onFinishCallback).toHaveBeenCalledTimes(1);
|
|
107
|
+
|
|
108
|
+
const callArgs = onFinishCallback.mock.calls[0][0];
|
|
109
|
+
expect(callArgs.isContinuation).toBe(false);
|
|
110
|
+
expect(callArgs.responseMessage.id).toBe('msg-456');
|
|
111
|
+
expect(callArgs.responseMessage.role).toBe('assistant');
|
|
112
|
+
expect(callArgs.messages).toHaveLength(2);
|
|
113
|
+
expect(callArgs.messages[0]).toEqual(originalMessages[0]);
|
|
114
|
+
expect(callArgs.messages[1]).toEqual(callArgs.responseMessage);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should handle empty original messages array', async () => {
|
|
118
|
+
const onFinishCallback = vi.fn();
|
|
119
|
+
const inputChunks: UIMessageChunk[] = [
|
|
120
|
+
{ type: 'start', messageId: 'msg-789' },
|
|
121
|
+
{ type: 'text-start', id: 'text-1' },
|
|
122
|
+
{ type: 'text-delta', id: 'text-1', delta: 'Response' },
|
|
123
|
+
{ type: 'text-end', id: 'text-1' },
|
|
124
|
+
{ type: 'finish' },
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
const stream = createUIMessageStream(inputChunks);
|
|
128
|
+
|
|
129
|
+
const resultStream = handleUIMessageStreamFinish<UIMessage>({
|
|
130
|
+
stream,
|
|
131
|
+
messageId: 'msg-789',
|
|
132
|
+
originalMessages: [],
|
|
133
|
+
onError: mockErrorHandler,
|
|
134
|
+
onFinish: onFinishCallback,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
await convertReadableStreamToArray(resultStream);
|
|
138
|
+
|
|
139
|
+
expect(onFinishCallback).toHaveBeenCalledTimes(1);
|
|
140
|
+
|
|
141
|
+
const callArgs = onFinishCallback.mock.calls[0][0];
|
|
142
|
+
expect(callArgs.isContinuation).toBe(false);
|
|
143
|
+
expect(callArgs.messages).toHaveLength(1);
|
|
144
|
+
expect(callArgs.messages[0]).toEqual(callArgs.responseMessage);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('stream processing with continuation scenario', () => {
|
|
149
|
+
it('should handle continuation when last message is assistant', async () => {
|
|
150
|
+
const onFinishCallback = vi.fn();
|
|
151
|
+
const inputChunks: UIMessageChunk[] = [
|
|
152
|
+
{ type: 'start', messageId: 'assistant-msg-1' },
|
|
153
|
+
{ type: 'text-start', id: 'text-1' },
|
|
154
|
+
{ type: 'text-delta', id: 'text-1', delta: ' continued' },
|
|
155
|
+
{ type: 'text-end', id: 'text-1' },
|
|
156
|
+
{ type: 'finish' },
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
const originalMessages: UIMessage[] = [
|
|
160
|
+
{
|
|
161
|
+
id: 'user-msg-1',
|
|
162
|
+
role: 'user',
|
|
163
|
+
parts: [{ type: 'text', text: 'Continue this' }],
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 'assistant-msg-1',
|
|
167
|
+
role: 'assistant',
|
|
168
|
+
parts: [{ type: 'text', text: 'This is' }],
|
|
169
|
+
},
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
const stream = createUIMessageStream(inputChunks);
|
|
173
|
+
|
|
174
|
+
const resultStream = handleUIMessageStreamFinish<UIMessage>({
|
|
175
|
+
stream,
|
|
176
|
+
messageId: 'msg-999', // this should be ignored since we're continuing
|
|
177
|
+
originalMessages,
|
|
178
|
+
onError: mockErrorHandler,
|
|
179
|
+
onFinish: onFinishCallback,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await convertReadableStreamToArray(resultStream);
|
|
183
|
+
|
|
184
|
+
expect(onFinishCallback).toHaveBeenCalledTimes(1);
|
|
185
|
+
|
|
186
|
+
const callArgs = onFinishCallback.mock.calls[0][0];
|
|
187
|
+
expect(callArgs.isContinuation).toBe(true);
|
|
188
|
+
expect(callArgs.responseMessage.id).toBe('assistant-msg-1'); // uses the existing assistant message id
|
|
189
|
+
expect(callArgs.messages).toHaveLength(2); // original user message + updated assistant message
|
|
190
|
+
expect(callArgs.messages[0]).toEqual(originalMessages[0]);
|
|
191
|
+
expect(callArgs.messages[1]).toEqual(callArgs.responseMessage);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should not treat user message as continuation', async () => {
|
|
195
|
+
const onFinishCallback = vi.fn();
|
|
196
|
+
const inputChunks: UIMessageChunk[] = [
|
|
197
|
+
{ type: 'start', messageId: 'msg-001' },
|
|
198
|
+
{ type: 'text-start', id: 'text-1' },
|
|
199
|
+
{ type: 'text-delta', id: 'text-1', delta: 'New response' },
|
|
200
|
+
{ type: 'text-end', id: 'text-1' },
|
|
201
|
+
{ type: 'finish' },
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
const originalMessages: UIMessage[] = [
|
|
205
|
+
{
|
|
206
|
+
id: 'user-msg-1',
|
|
207
|
+
role: 'user',
|
|
208
|
+
parts: [{ type: 'text', text: 'Question' }],
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: 'user-msg-2',
|
|
212
|
+
role: 'user',
|
|
213
|
+
parts: [{ type: 'text', text: 'Another question' }],
|
|
214
|
+
},
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
const stream = createUIMessageStream(inputChunks);
|
|
218
|
+
|
|
219
|
+
const resultStream = handleUIMessageStreamFinish<UIMessage>({
|
|
220
|
+
stream,
|
|
221
|
+
messageId: 'msg-001',
|
|
222
|
+
originalMessages,
|
|
223
|
+
onError: mockErrorHandler,
|
|
224
|
+
onFinish: onFinishCallback,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
await convertReadableStreamToArray(resultStream);
|
|
228
|
+
|
|
229
|
+
expect(onFinishCallback).toHaveBeenCalledTimes(1);
|
|
230
|
+
|
|
231
|
+
const callArgs = onFinishCallback.mock.calls[0][0];
|
|
232
|
+
expect(callArgs.isContinuation).toBe(false);
|
|
233
|
+
expect(callArgs.responseMessage.id).toBe('msg-001');
|
|
234
|
+
expect(callArgs.messages).toHaveLength(3); // 2 user messages + 1 new assistant message
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe('abort scenarios', () => {
|
|
239
|
+
it('should set isAborted to true when abort chunk is encountered', async () => {
|
|
240
|
+
const onFinishCallback = vi.fn();
|
|
241
|
+
const inputChunks: UIMessageChunk[] = [
|
|
242
|
+
{ type: 'start', messageId: 'msg-abort-1' },
|
|
243
|
+
{ type: 'text-start', id: 'text-1' },
|
|
244
|
+
{ type: 'text-delta', id: 'text-1', delta: 'Starting text' },
|
|
245
|
+
{ type: 'abort' },
|
|
246
|
+
{ type: 'finish' },
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
const originalMessages: UIMessage[] = [
|
|
250
|
+
{
|
|
251
|
+
id: 'user-msg-1',
|
|
252
|
+
role: 'user',
|
|
253
|
+
parts: [{ type: 'text', text: 'Test request' }],
|
|
254
|
+
},
|
|
255
|
+
];
|
|
256
|
+
|
|
257
|
+
const stream = createUIMessageStream(inputChunks);
|
|
258
|
+
|
|
259
|
+
const resultStream = handleUIMessageStreamFinish<UIMessage>({
|
|
260
|
+
stream,
|
|
261
|
+
messageId: 'msg-abort-1',
|
|
262
|
+
originalMessages,
|
|
263
|
+
onError: mockErrorHandler,
|
|
264
|
+
onFinish: onFinishCallback,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const result = await convertReadableStreamToArray(resultStream);
|
|
268
|
+
|
|
269
|
+
expect(result).toEqual(inputChunks);
|
|
270
|
+
expect(onFinishCallback).toHaveBeenCalledTimes(1);
|
|
271
|
+
|
|
272
|
+
const callArgs = onFinishCallback.mock.calls[0][0];
|
|
273
|
+
expect(callArgs.isAborted).toBe(true);
|
|
274
|
+
expect(callArgs.isContinuation).toBe(false);
|
|
275
|
+
expect(callArgs.responseMessage.id).toBe('msg-abort-1');
|
|
276
|
+
expect(callArgs.messages).toHaveLength(2);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should pass through abort reason when provided', async () => {
|
|
280
|
+
const onFinishCallback = vi.fn();
|
|
281
|
+
const inputChunks: UIMessageChunk[] = [
|
|
282
|
+
{ type: 'start', messageId: 'msg-abort-reason' },
|
|
283
|
+
{ type: 'text-start', id: 'text-1' },
|
|
284
|
+
{ type: 'text-delta', id: 'text-1', delta: 'Starting text' },
|
|
285
|
+
{ type: 'abort', reason: 'manual abort' },
|
|
286
|
+
{ type: 'finish' },
|
|
287
|
+
];
|
|
288
|
+
|
|
289
|
+
const stream = createUIMessageStream(inputChunks);
|
|
290
|
+
|
|
291
|
+
const resultStream = handleUIMessageStreamFinish<UIMessage>({
|
|
292
|
+
stream,
|
|
293
|
+
messageId: 'msg-abort-reason',
|
|
294
|
+
originalMessages: [],
|
|
295
|
+
onError: mockErrorHandler,
|
|
296
|
+
onFinish: onFinishCallback,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const result = await convertReadableStreamToArray(resultStream);
|
|
300
|
+
|
|
301
|
+
expect(result).toEqual(inputChunks);
|
|
302
|
+
expect(onFinishCallback).toHaveBeenCalledTimes(1);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should set isAborted to false when no abort chunk is encountered', async () => {
|
|
306
|
+
const onFinishCallback = vi.fn();
|
|
307
|
+
const inputChunks: UIMessageChunk[] = [
|
|
308
|
+
{ type: 'start', messageId: 'msg-normal' },
|
|
309
|
+
{ type: 'text-start', id: 'text-1' },
|
|
310
|
+
{ type: 'text-delta', id: 'text-1', delta: 'Complete text' },
|
|
311
|
+
{ type: 'text-end', id: 'text-1' },
|
|
312
|
+
{ type: 'finish' },
|
|
313
|
+
];
|
|
314
|
+
|
|
315
|
+
const originalMessages: UIMessage[] = [
|
|
316
|
+
{
|
|
317
|
+
id: 'user-msg-1',
|
|
318
|
+
role: 'user',
|
|
319
|
+
parts: [{ type: 'text', text: 'Test request' }],
|
|
320
|
+
},
|
|
321
|
+
];
|
|
322
|
+
|
|
323
|
+
const stream = createUIMessageStream(inputChunks);
|
|
324
|
+
|
|
325
|
+
const resultStream = handleUIMessageStreamFinish<UIMessage>({
|
|
326
|
+
stream,
|
|
327
|
+
messageId: 'msg-normal',
|
|
328
|
+
originalMessages,
|
|
329
|
+
onError: mockErrorHandler,
|
|
330
|
+
onFinish: onFinishCallback,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
await convertReadableStreamToArray(resultStream);
|
|
334
|
+
|
|
335
|
+
expect(onFinishCallback).toHaveBeenCalledTimes(1);
|
|
336
|
+
|
|
337
|
+
const callArgs = onFinishCallback.mock.calls[0][0];
|
|
338
|
+
expect(callArgs.isAborted).toBe(false);
|
|
339
|
+
expect(callArgs.isContinuation).toBe(false);
|
|
340
|
+
expect(callArgs.responseMessage.id).toBe('msg-normal');
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('should handle abort chunk in pass-through mode without onFinish', async () => {
|
|
344
|
+
const inputChunks: UIMessageChunk[] = [
|
|
345
|
+
{ type: 'start', messageId: 'msg-abort-passthrough' },
|
|
346
|
+
{ type: 'text-start', id: 'text-1' },
|
|
347
|
+
{ type: 'text-delta', id: 'text-1', delta: 'Text before abort' },
|
|
348
|
+
{ type: 'abort' },
|
|
349
|
+
{ type: 'finish' },
|
|
350
|
+
];
|
|
351
|
+
|
|
352
|
+
const stream = createUIMessageStream(inputChunks);
|
|
353
|
+
|
|
354
|
+
const resultStream = handleUIMessageStreamFinish<UIMessage>({
|
|
355
|
+
stream,
|
|
356
|
+
messageId: 'msg-abort-passthrough',
|
|
357
|
+
originalMessages: [],
|
|
358
|
+
onError: mockErrorHandler,
|
|
359
|
+
// onFinish is not provided
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
const result = await convertReadableStreamToArray(resultStream);
|
|
363
|
+
|
|
364
|
+
expect(result).toEqual(inputChunks);
|
|
365
|
+
expect(mockErrorHandler).not.toHaveBeenCalled();
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('should handle multiple abort chunks correctly', async () => {
|
|
369
|
+
const onFinishCallback = vi.fn();
|
|
370
|
+
const inputChunks: UIMessageChunk[] = [
|
|
371
|
+
{ type: 'start', messageId: 'msg-multiple-abort' },
|
|
372
|
+
{ type: 'text-start', id: 'text-1' },
|
|
373
|
+
{ type: 'abort' },
|
|
374
|
+
{ type: 'text-delta', id: 'text-1', delta: 'Some text' },
|
|
375
|
+
{ type: 'abort' },
|
|
376
|
+
{ type: 'finish' },
|
|
377
|
+
];
|
|
378
|
+
|
|
379
|
+
const stream = createUIMessageStream(inputChunks);
|
|
380
|
+
|
|
381
|
+
const resultStream = handleUIMessageStreamFinish<UIMessage>({
|
|
382
|
+
stream,
|
|
383
|
+
messageId: 'msg-multiple-abort',
|
|
384
|
+
originalMessages: [],
|
|
385
|
+
onError: mockErrorHandler,
|
|
386
|
+
onFinish: onFinishCallback,
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const result = await convertReadableStreamToArray(resultStream);
|
|
390
|
+
|
|
391
|
+
expect(result).toEqual(inputChunks);
|
|
392
|
+
expect(onFinishCallback).toHaveBeenCalledTimes(1);
|
|
393
|
+
|
|
394
|
+
const callArgs = onFinishCallback.mock.calls[0][0];
|
|
395
|
+
expect(callArgs.isAborted).toBe(true);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('should call onFinish when reader is cancelled (simulating browser close/navigation)', async () => {
|
|
399
|
+
const onFinishCallback = vi.fn();
|
|
400
|
+
|
|
401
|
+
const inputChunks: UIMessageChunk[] = [
|
|
402
|
+
{ type: 'start', messageId: 'msg-1' },
|
|
403
|
+
{ type: 'text-start', id: 'text-1' },
|
|
404
|
+
{ type: 'text-delta', id: 'text-1', delta: 'Hello' },
|
|
405
|
+
];
|
|
406
|
+
|
|
407
|
+
const stream = createUIMessageStream(inputChunks);
|
|
408
|
+
|
|
409
|
+
const resultStream = handleUIMessageStreamFinish<UIMessage>({
|
|
410
|
+
stream,
|
|
411
|
+
messageId: 'msg-1',
|
|
412
|
+
originalMessages: [],
|
|
413
|
+
onError: mockErrorHandler,
|
|
414
|
+
onFinish: onFinishCallback,
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const reader = resultStream.getReader();
|
|
418
|
+
await reader.read();
|
|
419
|
+
await reader.cancel();
|
|
420
|
+
reader.releaseLock();
|
|
421
|
+
|
|
422
|
+
expect(onFinishCallback).toHaveBeenCalledTimes(1);
|
|
423
|
+
|
|
424
|
+
const callArgs = onFinishCallback.mock.calls[0][0];
|
|
425
|
+
expect(callArgs.isAborted).toBe(false);
|
|
426
|
+
expect(callArgs.responseMessage.id).toBe('msg-1');
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
});
|