ai 6.0.33 → 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 +10 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +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,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A type that combines AsyncIterable and ReadableStream.
|
|
3
|
+
* This allows a ReadableStream to be consumed using for-await-of syntax.
|
|
4
|
+
*/
|
|
5
|
+
export type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Wraps a ReadableStream and returns an object that is both a ReadableStream and an AsyncIterable.
|
|
9
|
+
* This enables consumption of the stream using for-await-of, with proper resource cleanup on early exit or error.
|
|
10
|
+
*
|
|
11
|
+
* @template T The type of the stream's chunks.
|
|
12
|
+
* @param source The source ReadableStream to wrap.
|
|
13
|
+
* @returns An AsyncIterableStream that can be used as both a ReadableStream and an AsyncIterable.
|
|
14
|
+
*/
|
|
15
|
+
export function createAsyncIterableStream<T>(
|
|
16
|
+
source: ReadableStream<T>,
|
|
17
|
+
): AsyncIterableStream<T> {
|
|
18
|
+
// Pipe through a TransformStream to ensure a fresh, unlocked stream.
|
|
19
|
+
const stream = source.pipeThrough(new TransformStream<T, T>());
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Implements the async iterator protocol for the stream.
|
|
23
|
+
* Ensures proper cleanup (cancelling and releasing the reader) on completion, early exit, or error.
|
|
24
|
+
*/
|
|
25
|
+
(stream as AsyncIterableStream<T>)[Symbol.asyncIterator] = function (
|
|
26
|
+
this: ReadableStream<T>,
|
|
27
|
+
): AsyncIterator<T> {
|
|
28
|
+
const reader = this.getReader();
|
|
29
|
+
|
|
30
|
+
let finished = false;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Cleans up the reader by cancelling and releasing the lock.
|
|
34
|
+
*/
|
|
35
|
+
async function cleanup(cancelStream: boolean) {
|
|
36
|
+
if (finished) return;
|
|
37
|
+
|
|
38
|
+
finished = true;
|
|
39
|
+
try {
|
|
40
|
+
if (cancelStream) {
|
|
41
|
+
await reader.cancel?.();
|
|
42
|
+
}
|
|
43
|
+
} finally {
|
|
44
|
+
try {
|
|
45
|
+
reader.releaseLock();
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
/**
|
|
52
|
+
* Reads the next chunk from the stream.
|
|
53
|
+
* @returns A promise resolving to the next IteratorResult.
|
|
54
|
+
*/
|
|
55
|
+
async next(): Promise<IteratorResult<T>> {
|
|
56
|
+
if (finished) {
|
|
57
|
+
return { done: true, value: undefined };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { done, value } = await reader.read();
|
|
61
|
+
|
|
62
|
+
if (done) {
|
|
63
|
+
await cleanup(true);
|
|
64
|
+
return { done: true, value: undefined };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { done: false, value };
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* May be called on early exit (e.g., break from for-await) or after completion.
|
|
72
|
+
* Ensures the stream is cancelled and resources are released.
|
|
73
|
+
* @returns A promise resolving to a completed IteratorResult.
|
|
74
|
+
*/
|
|
75
|
+
async return(): Promise<IteratorResult<T>> {
|
|
76
|
+
await cleanup(true);
|
|
77
|
+
return { done: true, value: undefined };
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Called on early exit with error.
|
|
82
|
+
* Ensures the stream is cancelled and resources are released, then rethrows the error.
|
|
83
|
+
* @param err The error to throw.
|
|
84
|
+
* @returns A promise that rejects with the provided error.
|
|
85
|
+
*/
|
|
86
|
+
async throw(err: unknown): Promise<IteratorResult<T>> {
|
|
87
|
+
await cleanup(true);
|
|
88
|
+
throw err;
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return stream as AsyncIterableStream<T>;
|
|
94
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consumes a ReadableStream until it's fully read.
|
|
3
|
+
*
|
|
4
|
+
* This function reads the stream chunk by chunk until the stream is exhausted.
|
|
5
|
+
* It doesn't process or return the data from the stream; it simply ensures
|
|
6
|
+
* that the entire stream is read.
|
|
7
|
+
*
|
|
8
|
+
* @param {ReadableStream} stream - The ReadableStream to be consumed.
|
|
9
|
+
* @returns {Promise<void>} A promise that resolves when the stream is fully consumed.
|
|
10
|
+
*/
|
|
11
|
+
export async function consumeStream({
|
|
12
|
+
stream,
|
|
13
|
+
onError,
|
|
14
|
+
}: {
|
|
15
|
+
stream: ReadableStream;
|
|
16
|
+
onError?: (error: unknown) => void;
|
|
17
|
+
}): Promise<void> {
|
|
18
|
+
const reader = stream.getReader();
|
|
19
|
+
try {
|
|
20
|
+
while (true) {
|
|
21
|
+
const { done } = await reader.read();
|
|
22
|
+
if (done) break;
|
|
23
|
+
}
|
|
24
|
+
} catch (error) {
|
|
25
|
+
onError?.(error);
|
|
26
|
+
} finally {
|
|
27
|
+
reader.releaseLock();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { cosineSimilarity } from './cosine-similarity';
|
|
2
|
+
import { it, expect } from 'vitest';
|
|
3
|
+
|
|
4
|
+
it('should calculate cosine similarity correctly', () => {
|
|
5
|
+
const vector1 = [1, 2, 3];
|
|
6
|
+
const vector2 = [4, 5, 6];
|
|
7
|
+
|
|
8
|
+
const result = cosineSimilarity(vector1, vector2);
|
|
9
|
+
|
|
10
|
+
// test against pre-calculated value:
|
|
11
|
+
expect(result).toBeCloseTo(0.9746318461970762, 5);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should calculate negative cosine similarity correctly', () => {
|
|
15
|
+
const vector1 = [1, 0];
|
|
16
|
+
const vector2 = [-1, 0];
|
|
17
|
+
|
|
18
|
+
const result = cosineSimilarity(vector1, vector2);
|
|
19
|
+
|
|
20
|
+
// test against pre-calculated value:
|
|
21
|
+
expect(result).toBeCloseTo(-1, 5);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should throw an error when vectors have different lengths', () => {
|
|
25
|
+
const vector1 = [1, 2, 3];
|
|
26
|
+
const vector2 = [4, 5];
|
|
27
|
+
|
|
28
|
+
expect(() => cosineSimilarity(vector1, vector2)).toThrowError();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should give 0 when one of the vectors is a zero vector', () => {
|
|
32
|
+
const vector1 = [0, 1, 2];
|
|
33
|
+
const vector2 = [0, 0, 0];
|
|
34
|
+
|
|
35
|
+
const result = cosineSimilarity(vector1, vector2);
|
|
36
|
+
|
|
37
|
+
expect(result).toBe(0);
|
|
38
|
+
|
|
39
|
+
const result2 = cosineSimilarity(vector2, vector1);
|
|
40
|
+
|
|
41
|
+
expect(result2).toBe(0);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should handle vectors with very small magnitudes', () => {
|
|
45
|
+
const vector1 = [1e-10, 0, 0];
|
|
46
|
+
const vector2 = [2e-10, 0, 0];
|
|
47
|
+
|
|
48
|
+
const result = cosineSimilarity(vector1, vector2);
|
|
49
|
+
|
|
50
|
+
expect(result).toBe(1);
|
|
51
|
+
|
|
52
|
+
const vector3 = [1e-10, 0, 0];
|
|
53
|
+
const vector4 = [-1e-10, 0, 0];
|
|
54
|
+
|
|
55
|
+
const result2 = cosineSimilarity(vector3, vector4);
|
|
56
|
+
expect(result2).toBe(-1);
|
|
57
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { InvalidArgumentError } from '../error/invalid-argument-error';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Calculates the cosine similarity between two vectors. This is a useful metric for
|
|
5
|
+
* comparing the similarity of two vectors such as embeddings.
|
|
6
|
+
*
|
|
7
|
+
* @param vector1 - The first vector.
|
|
8
|
+
* @param vector2 - The second vector.
|
|
9
|
+
*
|
|
10
|
+
* @returns The cosine similarity between vector1 and vector2.
|
|
11
|
+
* @returns 0 if either vector is the zero vector.
|
|
12
|
+
*
|
|
13
|
+
* @throws {InvalidArgumentError} If the vectors do not have the same length.
|
|
14
|
+
*/
|
|
15
|
+
export function cosineSimilarity(vector1: number[], vector2: number[]): number {
|
|
16
|
+
if (vector1.length !== vector2.length) {
|
|
17
|
+
throw new InvalidArgumentError({
|
|
18
|
+
parameter: 'vector1,vector2',
|
|
19
|
+
value: { vector1Length: vector1.length, vector2Length: vector2.length },
|
|
20
|
+
message: `Vectors must have the same length`,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const n = vector1.length;
|
|
25
|
+
|
|
26
|
+
if (n === 0) {
|
|
27
|
+
return 0; // Return 0 for empty vectors if no error is thrown
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let magnitudeSquared1 = 0;
|
|
31
|
+
let magnitudeSquared2 = 0;
|
|
32
|
+
let dotProduct = 0;
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i < n; i++) {
|
|
35
|
+
const value1 = vector1[i];
|
|
36
|
+
const value2 = vector2[i];
|
|
37
|
+
|
|
38
|
+
magnitudeSquared1 += value1 * value1;
|
|
39
|
+
magnitudeSquared2 += value2 * value2;
|
|
40
|
+
dotProduct += value1 * value2;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return magnitudeSquared1 === 0 || magnitudeSquared2 === 0
|
|
44
|
+
? 0
|
|
45
|
+
: dotProduct /
|
|
46
|
+
(Math.sqrt(magnitudeSquared1) * Math.sqrt(magnitudeSquared2));
|
|
47
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ErrorHandler } from './error-handler';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a Promise with externally accessible resolve and reject functions.
|
|
5
|
+
*
|
|
6
|
+
* @template T - The type of the value that the Promise will resolve to.
|
|
7
|
+
* @returns An object containing:
|
|
8
|
+
* - promise: A Promise that can be resolved or rejected externally.
|
|
9
|
+
* - resolve: A function to resolve the Promise with a value of type T.
|
|
10
|
+
* - reject: A function to reject the Promise with an error.
|
|
11
|
+
*/
|
|
12
|
+
export function createResolvablePromise<T = any>(): {
|
|
13
|
+
promise: Promise<T>;
|
|
14
|
+
resolve: (value: T) => void;
|
|
15
|
+
reject: ErrorHandler;
|
|
16
|
+
} {
|
|
17
|
+
let resolve: (value: T) => void;
|
|
18
|
+
let reject: ErrorHandler;
|
|
19
|
+
|
|
20
|
+
const promise = new Promise<T>((res, rej) => {
|
|
21
|
+
resolve = res;
|
|
22
|
+
reject = rej;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
promise,
|
|
27
|
+
resolve: resolve!,
|
|
28
|
+
reject: reject!,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import {
|
|
2
|
+
convertArrayToReadableStream,
|
|
3
|
+
convertReadableStreamToArray,
|
|
4
|
+
} from '@ai-sdk/provider-utils/test';
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { createStitchableStream } from './create-stitchable-stream';
|
|
7
|
+
|
|
8
|
+
describe('createStitchableStream', () => {
|
|
9
|
+
describe('read full streams after they are added', () => {
|
|
10
|
+
it('should return no stream when immediately closed', async () => {
|
|
11
|
+
const { stream, close } = createStitchableStream<number>();
|
|
12
|
+
|
|
13
|
+
close();
|
|
14
|
+
|
|
15
|
+
expect(await convertReadableStreamToArray(stream)).toEqual([]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should return all values from a single inner stream', async () => {
|
|
19
|
+
const { stream, addStream, close } = createStitchableStream<number>();
|
|
20
|
+
|
|
21
|
+
addStream(convertArrayToReadableStream([1, 2, 3]));
|
|
22
|
+
close();
|
|
23
|
+
|
|
24
|
+
expect(await convertReadableStreamToArray(stream)).toEqual([1, 2, 3]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should return all values from 2 inner streams', async () => {
|
|
28
|
+
const { stream, addStream, close } = createStitchableStream<number>();
|
|
29
|
+
|
|
30
|
+
addStream(convertArrayToReadableStream([1, 2, 3]));
|
|
31
|
+
addStream(convertArrayToReadableStream([4, 5, 6]));
|
|
32
|
+
close();
|
|
33
|
+
|
|
34
|
+
expect(await convertReadableStreamToArray(stream)).toEqual([
|
|
35
|
+
1, 2, 3, 4, 5, 6,
|
|
36
|
+
]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should return all values from 3 inner streams', async () => {
|
|
40
|
+
const { stream, addStream, close } = createStitchableStream<number>();
|
|
41
|
+
|
|
42
|
+
addStream(convertArrayToReadableStream([1, 2, 3]));
|
|
43
|
+
addStream(convertArrayToReadableStream([4, 5, 6]));
|
|
44
|
+
addStream(convertArrayToReadableStream([7, 8, 9]));
|
|
45
|
+
close();
|
|
46
|
+
|
|
47
|
+
expect(await convertReadableStreamToArray(stream)).toEqual([
|
|
48
|
+
1, 2, 3, 4, 5, 6, 7, 8, 9,
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should handle empty inner streams', async () => {
|
|
53
|
+
const { stream, addStream, close } = createStitchableStream<number>();
|
|
54
|
+
|
|
55
|
+
addStream(convertArrayToReadableStream([]));
|
|
56
|
+
addStream(convertArrayToReadableStream([1, 2]));
|
|
57
|
+
addStream(convertArrayToReadableStream([]));
|
|
58
|
+
addStream(convertArrayToReadableStream([3, 4]));
|
|
59
|
+
close();
|
|
60
|
+
|
|
61
|
+
expect(await convertReadableStreamToArray(stream)).toEqual([1, 2, 3, 4]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should handle reading a single value before it is added', async () => {
|
|
65
|
+
const { stream, addStream, close } = createStitchableStream<number>();
|
|
66
|
+
|
|
67
|
+
// Start reading before any values are added
|
|
68
|
+
const reader = stream.getReader();
|
|
69
|
+
const readPromise = reader.read();
|
|
70
|
+
|
|
71
|
+
// Add value with delay after starting read
|
|
72
|
+
Promise.resolve().then(() => {
|
|
73
|
+
addStream(convertArrayToReadableStream([42]));
|
|
74
|
+
close();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Value should be returned once available
|
|
78
|
+
expect(await readPromise).toEqual({ done: false, value: 42 });
|
|
79
|
+
|
|
80
|
+
// Stream should complete after value is read
|
|
81
|
+
expect(await reader.read()).toEqual({ done: true, value: undefined });
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('read from partial stream and with interruptions', async () => {
|
|
86
|
+
it('should return all values from 2 inner streams', async () => {
|
|
87
|
+
const { stream, addStream, close } = createStitchableStream<number>();
|
|
88
|
+
|
|
89
|
+
// read 5 values from the stream before they are added
|
|
90
|
+
// (added asynchronously)
|
|
91
|
+
const reader = stream.getReader();
|
|
92
|
+
const results: Array<{ done: boolean; value?: number }> = [];
|
|
93
|
+
for (let i = 0; i < 5; i++) {
|
|
94
|
+
reader.read().then(result => {
|
|
95
|
+
results.push(result);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
addStream(convertArrayToReadableStream([1, 2, 3]));
|
|
100
|
+
addStream(convertArrayToReadableStream([4, 5]));
|
|
101
|
+
close();
|
|
102
|
+
|
|
103
|
+
// wait for the stream to finish via await:
|
|
104
|
+
expect(await reader.read()).toEqual({ done: true, value: undefined });
|
|
105
|
+
|
|
106
|
+
expect(results).toEqual([
|
|
107
|
+
{ done: false, value: 1 },
|
|
108
|
+
{ done: false, value: 2 },
|
|
109
|
+
{ done: false, value: 3 },
|
|
110
|
+
{ done: false, value: 4 },
|
|
111
|
+
{ done: false, value: 5 },
|
|
112
|
+
]);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('error handling', () => {
|
|
117
|
+
it('should handle errors from inner streams', async () => {
|
|
118
|
+
const { stream, addStream, close } = createStitchableStream<number>();
|
|
119
|
+
|
|
120
|
+
const errorStream = new ReadableStream({
|
|
121
|
+
start(controller) {
|
|
122
|
+
controller.error(new Error('Test error'));
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
addStream(convertArrayToReadableStream([1, 2]));
|
|
127
|
+
addStream(errorStream);
|
|
128
|
+
addStream(convertArrayToReadableStream([3, 4]));
|
|
129
|
+
close();
|
|
130
|
+
|
|
131
|
+
await expect(convertReadableStreamToArray(stream)).rejects.toThrow(
|
|
132
|
+
'Test error',
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('cancellation & closing', () => {
|
|
138
|
+
it('should cancel all inner streams when cancelled', async () => {
|
|
139
|
+
const { stream, addStream } = createStitchableStream<number>();
|
|
140
|
+
|
|
141
|
+
let stream1Cancelled = false;
|
|
142
|
+
let stream2Cancelled = false;
|
|
143
|
+
|
|
144
|
+
const mockStream1 = new ReadableStream({
|
|
145
|
+
start(controller) {
|
|
146
|
+
controller.enqueue(1);
|
|
147
|
+
controller.enqueue(2);
|
|
148
|
+
},
|
|
149
|
+
cancel() {
|
|
150
|
+
stream1Cancelled = true;
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const mockStream2 = new ReadableStream({
|
|
155
|
+
start(controller) {
|
|
156
|
+
controller.enqueue(3);
|
|
157
|
+
controller.enqueue(4);
|
|
158
|
+
},
|
|
159
|
+
cancel() {
|
|
160
|
+
stream2Cancelled = true;
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
addStream(mockStream1);
|
|
165
|
+
addStream(mockStream2);
|
|
166
|
+
|
|
167
|
+
await stream.cancel();
|
|
168
|
+
|
|
169
|
+
expect(stream1Cancelled).toBe(true);
|
|
170
|
+
expect(stream2Cancelled).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should throw an error when adding a stream after closing', async () => {
|
|
174
|
+
const { addStream, close } = createStitchableStream<number>();
|
|
175
|
+
|
|
176
|
+
close();
|
|
177
|
+
|
|
178
|
+
expect(() => addStream(convertArrayToReadableStream([1, 2]))).toThrow(
|
|
179
|
+
'Cannot add inner stream: outer stream is closed',
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('terminate', () => {
|
|
185
|
+
it('should immediately close the stream and cancel all inner streams', async () => {
|
|
186
|
+
const { stream, addStream, terminate } = createStitchableStream<number>();
|
|
187
|
+
|
|
188
|
+
let stream1Cancelled = false;
|
|
189
|
+
let stream2Cancelled = false;
|
|
190
|
+
|
|
191
|
+
const mockStream1 = new ReadableStream({
|
|
192
|
+
start(controller) {
|
|
193
|
+
controller.enqueue(1);
|
|
194
|
+
controller.enqueue(2);
|
|
195
|
+
},
|
|
196
|
+
cancel() {
|
|
197
|
+
stream1Cancelled = true;
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const mockStream2 = new ReadableStream({
|
|
202
|
+
start(controller) {
|
|
203
|
+
controller.enqueue(3);
|
|
204
|
+
controller.enqueue(4);
|
|
205
|
+
},
|
|
206
|
+
cancel() {
|
|
207
|
+
stream2Cancelled = true;
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
addStream(mockStream1);
|
|
212
|
+
addStream(mockStream2);
|
|
213
|
+
|
|
214
|
+
// Start reading from the stream
|
|
215
|
+
const reader = stream.getReader();
|
|
216
|
+
const firstRead = await reader.read();
|
|
217
|
+
|
|
218
|
+
terminate();
|
|
219
|
+
|
|
220
|
+
// Should immediately close without reading remaining values
|
|
221
|
+
const finalRead = await reader.read();
|
|
222
|
+
|
|
223
|
+
expect(firstRead).toEqual({ done: false, value: 1 });
|
|
224
|
+
expect(finalRead).toEqual({ done: true, value: undefined });
|
|
225
|
+
expect(stream1Cancelled).toBe(true);
|
|
226
|
+
expect(stream2Cancelled).toBe(true);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('should throw an error when adding a stream after terminating', async () => {
|
|
230
|
+
const { addStream, terminate } = createStitchableStream<number>();
|
|
231
|
+
|
|
232
|
+
terminate();
|
|
233
|
+
|
|
234
|
+
expect(() => addStream(convertArrayToReadableStream([1, 2]))).toThrow(
|
|
235
|
+
'Cannot add inner stream: outer stream is closed',
|
|
236
|
+
);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { createResolvablePromise } from './create-resolvable-promise';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a stitchable stream that can pipe one stream at a time.
|
|
5
|
+
*
|
|
6
|
+
* @template T - The type of values emitted by the streams.
|
|
7
|
+
* @returns {Object} An object containing the stitchable stream and control methods.
|
|
8
|
+
*/
|
|
9
|
+
export function createStitchableStream<T>(): {
|
|
10
|
+
stream: ReadableStream<T>;
|
|
11
|
+
addStream: (innerStream: ReadableStream<T>) => void;
|
|
12
|
+
close: () => void;
|
|
13
|
+
terminate: () => void;
|
|
14
|
+
} {
|
|
15
|
+
let innerStreamReaders: ReadableStreamDefaultReader<T>[] = [];
|
|
16
|
+
let controller: ReadableStreamDefaultController<T> | null = null;
|
|
17
|
+
let isClosed = false;
|
|
18
|
+
let waitForNewStream = createResolvablePromise<void>();
|
|
19
|
+
|
|
20
|
+
const terminate = () => {
|
|
21
|
+
isClosed = true;
|
|
22
|
+
waitForNewStream.resolve();
|
|
23
|
+
|
|
24
|
+
innerStreamReaders.forEach(reader => reader.cancel());
|
|
25
|
+
innerStreamReaders = [];
|
|
26
|
+
controller?.close();
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const processPull = async () => {
|
|
30
|
+
// Case 1: Outer stream is closed and no more inner streams
|
|
31
|
+
if (isClosed && innerStreamReaders.length === 0) {
|
|
32
|
+
controller?.close();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Case 2: No inner streams available, but outer stream is open
|
|
37
|
+
// wait for a new inner stream to be added or the outer stream to close
|
|
38
|
+
if (innerStreamReaders.length === 0) {
|
|
39
|
+
waitForNewStream = createResolvablePromise<void>();
|
|
40
|
+
await waitForNewStream.promise;
|
|
41
|
+
return processPull();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const { value, done } = await innerStreamReaders[0].read();
|
|
46
|
+
|
|
47
|
+
if (done) {
|
|
48
|
+
// Case 3: Current inner stream is done
|
|
49
|
+
innerStreamReaders.shift(); // Remove the finished stream
|
|
50
|
+
|
|
51
|
+
if (innerStreamReaders.length === 0 && isClosed) {
|
|
52
|
+
// when closed and no more inner streams, stop pulling
|
|
53
|
+
controller?.close();
|
|
54
|
+
} else {
|
|
55
|
+
// continue pulling from the next stream
|
|
56
|
+
await processPull();
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
// Case 4: Current inner stream returns an item
|
|
60
|
+
controller?.enqueue(value);
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
// Case 5: Current inner stream throws an error
|
|
64
|
+
controller?.error(error);
|
|
65
|
+
innerStreamReaders.shift(); // Remove the errored stream
|
|
66
|
+
terminate(); // we have errored, terminate all streams
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
stream: new ReadableStream<T>({
|
|
72
|
+
start(controllerParam) {
|
|
73
|
+
controller = controllerParam;
|
|
74
|
+
},
|
|
75
|
+
pull: processPull,
|
|
76
|
+
async cancel() {
|
|
77
|
+
for (const reader of innerStreamReaders) {
|
|
78
|
+
await reader.cancel();
|
|
79
|
+
}
|
|
80
|
+
innerStreamReaders = [];
|
|
81
|
+
isClosed = true;
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
addStream: (innerStream: ReadableStream<T>) => {
|
|
85
|
+
if (isClosed) {
|
|
86
|
+
throw new Error('Cannot add inner stream: outer stream is closed');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
innerStreamReaders.push(innerStream.getReader());
|
|
90
|
+
waitForNewStream.resolve();
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Gracefully close the outer stream. This will let the inner streams
|
|
95
|
+
* finish processing and then close the outer stream.
|
|
96
|
+
*/
|
|
97
|
+
close: () => {
|
|
98
|
+
isClosed = true;
|
|
99
|
+
waitForNewStream.resolve();
|
|
100
|
+
|
|
101
|
+
if (innerStreamReaders.length === 0) {
|
|
102
|
+
controller?.close();
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Immediately close the outer stream. This will cancel all inner streams
|
|
108
|
+
* and close the outer stream.
|
|
109
|
+
*/
|
|
110
|
+
terminate,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a data URL of type text/* to a text string.
|
|
3
|
+
*/
|
|
4
|
+
export function getTextFromDataUrl(dataUrl: string): string {
|
|
5
|
+
const [header, base64Content] = dataUrl.split(',');
|
|
6
|
+
const mediaType = header.split(';')[0].split(':')[1];
|
|
7
|
+
|
|
8
|
+
if (mediaType == null || base64Content == null) {
|
|
9
|
+
throw new Error('Invalid data URL format');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
return window.atob(base64Content);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
throw new Error(`Error decoding data URL`);
|
|
16
|
+
}
|
|
17
|
+
}
|