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,155 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { mergeAbortSignals } from './merge-abort-signals';
|
|
3
|
+
|
|
4
|
+
describe('mergeAbortSignals', () => {
|
|
5
|
+
it('should return a signal that is initially not aborted', () => {
|
|
6
|
+
const controller1 = new AbortController();
|
|
7
|
+
const controller2 = new AbortController();
|
|
8
|
+
|
|
9
|
+
const merged = mergeAbortSignals(controller1.signal, controller2.signal);
|
|
10
|
+
|
|
11
|
+
expect(merged!.aborted).toBe(false);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should abort when the first signal aborts', () => {
|
|
15
|
+
const controller1 = new AbortController();
|
|
16
|
+
const controller2 = new AbortController();
|
|
17
|
+
|
|
18
|
+
const merged = mergeAbortSignals(controller1.signal, controller2.signal);
|
|
19
|
+
|
|
20
|
+
controller1.abort();
|
|
21
|
+
|
|
22
|
+
expect(merged!.aborted).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should abort when the second signal aborts', () => {
|
|
26
|
+
const controller1 = new AbortController();
|
|
27
|
+
const controller2 = new AbortController();
|
|
28
|
+
|
|
29
|
+
const merged = mergeAbortSignals(controller1.signal, controller2.signal);
|
|
30
|
+
|
|
31
|
+
controller2.abort();
|
|
32
|
+
|
|
33
|
+
expect(merged!.aborted).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should preserve the abort reason from the triggering signal', () => {
|
|
37
|
+
const controller1 = new AbortController();
|
|
38
|
+
const controller2 = new AbortController();
|
|
39
|
+
const reason = new Error('custom abort reason');
|
|
40
|
+
|
|
41
|
+
const merged = mergeAbortSignals(controller1.signal, controller2.signal);
|
|
42
|
+
|
|
43
|
+
controller1.abort(reason);
|
|
44
|
+
|
|
45
|
+
expect(merged!.reason).toBe(reason);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should preserve string abort reason', () => {
|
|
49
|
+
const controller1 = new AbortController();
|
|
50
|
+
|
|
51
|
+
const merged = mergeAbortSignals(controller1.signal);
|
|
52
|
+
|
|
53
|
+
controller1.abort('string reason');
|
|
54
|
+
|
|
55
|
+
expect(merged!.reason).toBe('string reason');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should handle already-aborted signals', () => {
|
|
59
|
+
const controller1 = new AbortController();
|
|
60
|
+
const reason = new Error('already aborted');
|
|
61
|
+
controller1.abort(reason);
|
|
62
|
+
|
|
63
|
+
const merged = mergeAbortSignals(controller1.signal);
|
|
64
|
+
|
|
65
|
+
expect(merged!.aborted).toBe(true);
|
|
66
|
+
expect(merged!.reason).toBe(reason);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should use the first already-aborted signal reason when multiple are aborted', () => {
|
|
70
|
+
const controller1 = new AbortController();
|
|
71
|
+
const controller2 = new AbortController();
|
|
72
|
+
const reason1 = new Error('first reason');
|
|
73
|
+
const reason2 = new Error('second reason');
|
|
74
|
+
|
|
75
|
+
controller1.abort(reason1);
|
|
76
|
+
controller2.abort(reason2);
|
|
77
|
+
|
|
78
|
+
const merged = mergeAbortSignals(controller1.signal, controller2.signal);
|
|
79
|
+
|
|
80
|
+
expect(merged!.aborted).toBe(true);
|
|
81
|
+
expect(merged!.reason).toBe(reason1);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should return undefined when no signals provided', () => {
|
|
85
|
+
const merged = mergeAbortSignals();
|
|
86
|
+
|
|
87
|
+
expect(merged).toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should return undefined when only null/undefined signals provided', () => {
|
|
91
|
+
const merged = mergeAbortSignals(null, undefined, null);
|
|
92
|
+
|
|
93
|
+
expect(merged).toBeUndefined();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should filter out null and undefined signals', () => {
|
|
97
|
+
const controller = new AbortController();
|
|
98
|
+
const reason = new Error('abort reason');
|
|
99
|
+
|
|
100
|
+
const merged = mergeAbortSignals(null, controller.signal, undefined);
|
|
101
|
+
|
|
102
|
+
expect(merged).not.toBeUndefined();
|
|
103
|
+
expect(merged!.aborted).toBe(false);
|
|
104
|
+
|
|
105
|
+
controller.abort(reason);
|
|
106
|
+
|
|
107
|
+
expect(merged!.aborted).toBe(true);
|
|
108
|
+
expect(merged!.reason).toBe(reason);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should return the signal directly when only one valid signal provided', () => {
|
|
112
|
+
const controller = new AbortController();
|
|
113
|
+
|
|
114
|
+
const merged = mergeAbortSignals(null, controller.signal, undefined);
|
|
115
|
+
|
|
116
|
+
expect(merged).toBe(controller.signal);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should use the first aborting signal reason when multiple abort simultaneously', () => {
|
|
120
|
+
const controller1 = new AbortController();
|
|
121
|
+
const controller2 = new AbortController();
|
|
122
|
+
const reason1 = new Error('first reason');
|
|
123
|
+
const reason2 = new Error('second reason');
|
|
124
|
+
|
|
125
|
+
const merged = mergeAbortSignals(controller1.signal, controller2.signal);
|
|
126
|
+
|
|
127
|
+
// Both abort, but the first one's listener was registered first
|
|
128
|
+
controller1.abort(reason1);
|
|
129
|
+
controller2.abort(reason2);
|
|
130
|
+
|
|
131
|
+
expect(merged!.reason).toBe(reason1);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should return the original signal when only one signal provided', () => {
|
|
135
|
+
const controller = new AbortController();
|
|
136
|
+
|
|
137
|
+
const merged = mergeAbortSignals(controller.signal);
|
|
138
|
+
|
|
139
|
+
expect(merged).toBe(controller.signal);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should work with many signals', () => {
|
|
143
|
+
const controllers = Array.from({ length: 10 }, () => new AbortController());
|
|
144
|
+
const reason = new Error('signal 5 reason');
|
|
145
|
+
|
|
146
|
+
const merged = mergeAbortSignals(...controllers.map(c => c.signal));
|
|
147
|
+
|
|
148
|
+
expect(merged!.aborted).toBe(false);
|
|
149
|
+
|
|
150
|
+
controllers[5].abort(reason);
|
|
151
|
+
|
|
152
|
+
expect(merged!.aborted).toBe(true);
|
|
153
|
+
expect(merged!.reason).toBe(reason);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merges multiple AbortSignals into a single AbortSignal.
|
|
3
|
+
* The returned signal will abort when any of the input signals abort,
|
|
4
|
+
* with the same reason as the first signal to abort.
|
|
5
|
+
*
|
|
6
|
+
* @param signals - The AbortSignals to merge. Null and undefined values are filtered out.
|
|
7
|
+
* @returns An AbortSignal that aborts when any of the input signals abort,
|
|
8
|
+
* or undefined if no valid signals are provided.
|
|
9
|
+
*/
|
|
10
|
+
export function mergeAbortSignals(
|
|
11
|
+
...signals: (AbortSignal | null | undefined)[]
|
|
12
|
+
): AbortSignal | undefined {
|
|
13
|
+
const validSignals = signals.filter(
|
|
14
|
+
(signal): signal is AbortSignal => signal != null,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
if (validSignals.length === 0) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (validSignals.length === 1) {
|
|
22
|
+
return validSignals[0];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
|
|
27
|
+
for (const signal of validSignals) {
|
|
28
|
+
if (signal.aborted) {
|
|
29
|
+
controller.abort(signal.reason);
|
|
30
|
+
return controller.signal;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
signal.addEventListener(
|
|
34
|
+
'abort',
|
|
35
|
+
() => {
|
|
36
|
+
controller.abort(signal.reason);
|
|
37
|
+
},
|
|
38
|
+
{ once: true },
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return controller.signal;
|
|
43
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mergeObjects } from './merge-objects';
|
|
3
|
+
|
|
4
|
+
describe('mergeObjects', () => {
|
|
5
|
+
it('should merge two flat objects', () => {
|
|
6
|
+
const target = { a: 1, b: 2 };
|
|
7
|
+
const source = { b: 3, c: 4 };
|
|
8
|
+
const result = mergeObjects(target, source);
|
|
9
|
+
|
|
10
|
+
expect(result).toEqual({ a: 1, b: 3, c: 4 });
|
|
11
|
+
// Original objects should not be modified
|
|
12
|
+
expect(target).toEqual({ a: 1, b: 2 });
|
|
13
|
+
expect(source).toEqual({ b: 3, c: 4 });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should deeply merge nested objects', () => {
|
|
17
|
+
const target = { a: 1, b: { c: 2, d: 3 } };
|
|
18
|
+
const source = { b: { c: 4, e: 5 } };
|
|
19
|
+
const result = mergeObjects(target, source);
|
|
20
|
+
|
|
21
|
+
expect(result).toEqual({ a: 1, b: { c: 4, d: 3, e: 5 } });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should replace arrays instead of merging them', () => {
|
|
25
|
+
const target = { a: [1, 2, 3], b: 2 };
|
|
26
|
+
const source = { a: [4, 5] };
|
|
27
|
+
const result = mergeObjects(target, source);
|
|
28
|
+
|
|
29
|
+
expect(result).toEqual({ a: [4, 5], b: 2 });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should handle null and undefined values', () => {
|
|
33
|
+
const target = { a: 1, b: null, c: undefined };
|
|
34
|
+
const source = { a: null, b: 2, d: undefined };
|
|
35
|
+
const result = mergeObjects(target, source);
|
|
36
|
+
|
|
37
|
+
expect(result).toEqual({ a: null, b: 2, c: undefined, d: undefined });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should handle complex nested structures', () => {
|
|
41
|
+
const target = {
|
|
42
|
+
a: 1,
|
|
43
|
+
b: {
|
|
44
|
+
c: [1, 2, 3],
|
|
45
|
+
d: {
|
|
46
|
+
e: 4,
|
|
47
|
+
f: 5,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
const source = {
|
|
52
|
+
b: {
|
|
53
|
+
c: [4, 5],
|
|
54
|
+
d: {
|
|
55
|
+
f: 6,
|
|
56
|
+
g: 7,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
h: 8,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const result = mergeObjects(target, source);
|
|
63
|
+
|
|
64
|
+
expect(result).toEqual({
|
|
65
|
+
a: 1,
|
|
66
|
+
b: {
|
|
67
|
+
c: [4, 5],
|
|
68
|
+
d: {
|
|
69
|
+
e: 4,
|
|
70
|
+
f: 6,
|
|
71
|
+
g: 7,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
h: 8,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should handle Date objects', () => {
|
|
79
|
+
const date1 = new Date('2023-01-01');
|
|
80
|
+
const date2 = new Date('2023-02-01');
|
|
81
|
+
|
|
82
|
+
const target = { a: date1 };
|
|
83
|
+
const source = { a: date2 };
|
|
84
|
+
const result = mergeObjects(target, source);
|
|
85
|
+
|
|
86
|
+
expect(result?.a).toBe(date2);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should handle RegExp objects', () => {
|
|
90
|
+
const regex1 = /abc/;
|
|
91
|
+
const regex2 = /def/;
|
|
92
|
+
|
|
93
|
+
const target = { a: regex1 };
|
|
94
|
+
const source = { a: regex2 };
|
|
95
|
+
const result = mergeObjects(target, source);
|
|
96
|
+
|
|
97
|
+
expect(result?.a).toBe(regex2);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should handle empty objects', () => {
|
|
101
|
+
const target = {};
|
|
102
|
+
const source = { a: 1 };
|
|
103
|
+
expect(mergeObjects(target, source)).toEqual({ a: 1 });
|
|
104
|
+
|
|
105
|
+
const target2 = { a: 1 };
|
|
106
|
+
const source2 = {};
|
|
107
|
+
expect(mergeObjects(target2, source2)).toEqual({ a: 1 });
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should handle undefined inputs', () => {
|
|
111
|
+
// Both inputs undefined
|
|
112
|
+
expect(mergeObjects(undefined, undefined)).toBeUndefined();
|
|
113
|
+
|
|
114
|
+
// One input undefined
|
|
115
|
+
expect(mergeObjects({ a: 1 }, undefined)).toEqual({ a: 1 });
|
|
116
|
+
expect(mergeObjects(undefined, { b: 2 })).toEqual({ b: 2 });
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deeply merges two objects together.
|
|
3
|
+
* - Properties from the `overrides` object override those in the `base` object with the same key.
|
|
4
|
+
* - For nested objects, the merge is performed recursively (deep merge).
|
|
5
|
+
* - Arrays are replaced, not merged.
|
|
6
|
+
* - Primitive values are replaced.
|
|
7
|
+
* - If both `base` and `overrides` are undefined, returns undefined.
|
|
8
|
+
* - If one of `base` or `overrides` is undefined, returns the other.
|
|
9
|
+
*
|
|
10
|
+
* @param base The target object to merge into
|
|
11
|
+
* @param overrides The source object to merge from
|
|
12
|
+
* @returns A new object with the merged properties, or undefined if both inputs are undefined
|
|
13
|
+
*/
|
|
14
|
+
export function mergeObjects<T extends object, U extends object>(
|
|
15
|
+
base: T | undefined,
|
|
16
|
+
overrides: U | undefined,
|
|
17
|
+
): (T & U) | T | U | undefined {
|
|
18
|
+
// If both inputs are undefined, return undefined
|
|
19
|
+
if (base === undefined && overrides === undefined) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// If target is undefined, return source
|
|
24
|
+
if (base === undefined) {
|
|
25
|
+
return overrides;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// If source is undefined, return target
|
|
29
|
+
if (overrides === undefined) {
|
|
30
|
+
return base;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Create a new object to avoid mutating the inputs
|
|
34
|
+
const result = { ...base } as T & U;
|
|
35
|
+
|
|
36
|
+
// Iterate through all keys in the source object
|
|
37
|
+
for (const key in overrides) {
|
|
38
|
+
if (Object.prototype.hasOwnProperty.call(overrides, key)) {
|
|
39
|
+
const overridesValue = overrides[key];
|
|
40
|
+
|
|
41
|
+
// Skip if the overrides value is undefined
|
|
42
|
+
if (overridesValue === undefined) continue;
|
|
43
|
+
|
|
44
|
+
// Get the base value if it exists
|
|
45
|
+
const baseValue =
|
|
46
|
+
key in base ? base[key as unknown as keyof T] : undefined;
|
|
47
|
+
|
|
48
|
+
// Check if both values are objects that can be deeply merged
|
|
49
|
+
const isSourceObject =
|
|
50
|
+
overridesValue !== null &&
|
|
51
|
+
typeof overridesValue === 'object' &&
|
|
52
|
+
!Array.isArray(overridesValue) &&
|
|
53
|
+
!(overridesValue instanceof Date) &&
|
|
54
|
+
!(overridesValue instanceof RegExp);
|
|
55
|
+
|
|
56
|
+
const isTargetObject =
|
|
57
|
+
baseValue !== null &&
|
|
58
|
+
baseValue !== undefined &&
|
|
59
|
+
typeof baseValue === 'object' &&
|
|
60
|
+
!Array.isArray(baseValue) &&
|
|
61
|
+
!(baseValue instanceof Date) &&
|
|
62
|
+
!(baseValue instanceof RegExp);
|
|
63
|
+
|
|
64
|
+
// If both values are mergeable objects, merge them recursively
|
|
65
|
+
if (isSourceObject && isTargetObject) {
|
|
66
|
+
result[key as keyof (T & U)] = mergeObjects(
|
|
67
|
+
baseValue as object,
|
|
68
|
+
overridesValue as object,
|
|
69
|
+
) as any;
|
|
70
|
+
} else {
|
|
71
|
+
// For primitives, arrays, or when one value is not a mergeable object,
|
|
72
|
+
// simply override with the source value
|
|
73
|
+
result[key as keyof (T & U)] = overridesValue as any;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return result;
|
|
79
|
+
}
|
package/src/util/now.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { safeParseJSON } from '@ai-sdk/provider-utils';
|
|
2
|
+
import { fixJson } from './fix-json';
|
|
3
|
+
import { parsePartialJson } from './parse-partial-json';
|
|
4
|
+
import { JSONParseError } from '@ai-sdk/provider';
|
|
5
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
6
|
+
|
|
7
|
+
vi.mock('@ai-sdk/provider-utils');
|
|
8
|
+
vi.mock('./fix-json');
|
|
9
|
+
|
|
10
|
+
describe('parsePartialJson', () => {
|
|
11
|
+
it('should handle nullish input', async () => {
|
|
12
|
+
expect(await parsePartialJson(undefined)).toEqual({
|
|
13
|
+
value: undefined,
|
|
14
|
+
state: 'undefined-input',
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should parse valid JSON', async () => {
|
|
19
|
+
const validJson = '{"key": "value"}';
|
|
20
|
+
const parsedValue = { key: 'value' };
|
|
21
|
+
|
|
22
|
+
vi.mocked(safeParseJSON).mockResolvedValueOnce({
|
|
23
|
+
success: true,
|
|
24
|
+
value: parsedValue,
|
|
25
|
+
rawValue: parsedValue,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
expect(await parsePartialJson(validJson)).toEqual({
|
|
29
|
+
value: parsedValue,
|
|
30
|
+
state: 'successful-parse',
|
|
31
|
+
});
|
|
32
|
+
expect(safeParseJSON).toHaveBeenCalledWith({ text: validJson });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should repair and parse partial JSON', async () => {
|
|
36
|
+
const partialJson = '{"key": "value"';
|
|
37
|
+
const fixedJson = '{"key": "value"}';
|
|
38
|
+
const parsedValue = { key: 'value' };
|
|
39
|
+
|
|
40
|
+
vi.mocked(safeParseJSON)
|
|
41
|
+
.mockResolvedValueOnce({
|
|
42
|
+
success: false,
|
|
43
|
+
error: new JSONParseError({ text: partialJson, cause: undefined }),
|
|
44
|
+
rawValue: partialJson,
|
|
45
|
+
})
|
|
46
|
+
.mockResolvedValueOnce({
|
|
47
|
+
success: true,
|
|
48
|
+
value: parsedValue,
|
|
49
|
+
rawValue: parsedValue,
|
|
50
|
+
});
|
|
51
|
+
vi.mocked(fixJson).mockReturnValueOnce(fixedJson);
|
|
52
|
+
|
|
53
|
+
expect(await parsePartialJson(partialJson)).toEqual({
|
|
54
|
+
value: parsedValue,
|
|
55
|
+
state: 'repaired-parse',
|
|
56
|
+
});
|
|
57
|
+
expect(safeParseJSON).toHaveBeenCalledWith({ text: partialJson });
|
|
58
|
+
expect(fixJson).toHaveBeenCalledWith(partialJson);
|
|
59
|
+
expect(safeParseJSON).toHaveBeenCalledWith({ text: fixedJson });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should handle invalid JSON that cannot be repaired', async () => {
|
|
63
|
+
const invalidJson = 'not json at all';
|
|
64
|
+
|
|
65
|
+
vi.mocked(safeParseJSON).mockResolvedValue({
|
|
66
|
+
success: false,
|
|
67
|
+
error: new JSONParseError({ text: invalidJson, cause: undefined }),
|
|
68
|
+
rawValue: invalidJson,
|
|
69
|
+
});
|
|
70
|
+
vi.mocked(fixJson).mockReturnValueOnce(invalidJson);
|
|
71
|
+
|
|
72
|
+
expect(await parsePartialJson(invalidJson)).toEqual({
|
|
73
|
+
value: undefined,
|
|
74
|
+
state: 'failed-parse',
|
|
75
|
+
});
|
|
76
|
+
expect(safeParseJSON).toHaveBeenCalledWith({ text: invalidJson });
|
|
77
|
+
expect(fixJson).toHaveBeenCalledWith(invalidJson);
|
|
78
|
+
expect(safeParseJSON).toHaveBeenCalledWith({ text: invalidJson });
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { JSONValue } from '@ai-sdk/provider';
|
|
2
|
+
import { safeParseJSON } from '@ai-sdk/provider-utils';
|
|
3
|
+
import { fixJson } from './fix-json';
|
|
4
|
+
|
|
5
|
+
export async function parsePartialJson(jsonText: string | undefined): Promise<{
|
|
6
|
+
value: JSONValue | undefined;
|
|
7
|
+
state:
|
|
8
|
+
| 'undefined-input'
|
|
9
|
+
| 'successful-parse'
|
|
10
|
+
| 'repaired-parse'
|
|
11
|
+
| 'failed-parse';
|
|
12
|
+
}> {
|
|
13
|
+
if (jsonText === undefined) {
|
|
14
|
+
return { value: undefined, state: 'undefined-input' };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let result = await safeParseJSON({ text: jsonText });
|
|
18
|
+
|
|
19
|
+
if (result.success) {
|
|
20
|
+
return { value: result.value, state: 'successful-parse' };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
result = await safeParseJSON({ text: fixJson(jsonText) });
|
|
24
|
+
|
|
25
|
+
if (result.success) {
|
|
26
|
+
return { value: result.value, state: 'repaired-parse' };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { value: undefined, state: 'failed-parse' };
|
|
30
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { prepareHeaders } from './prepare-headers';
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
|
|
4
|
+
describe('prepareHeaders', () => {
|
|
5
|
+
it('should set Content-Type header if not present', () => {
|
|
6
|
+
const headers = prepareHeaders({}, { 'content-type': 'application/json' });
|
|
7
|
+
|
|
8
|
+
expect(headers.get('Content-Type')).toBe('application/json');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should not overwrite existing Content-Type header', () => {
|
|
12
|
+
const headers = prepareHeaders(
|
|
13
|
+
{ 'Content-Type': 'text/html' },
|
|
14
|
+
{ 'content-type': 'application/json' },
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
expect(headers.get('Content-Type')).toBe('text/html');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should handle undefined init', () => {
|
|
21
|
+
const headers = prepareHeaders(undefined, {
|
|
22
|
+
'content-type': 'application/json',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
expect(headers.get('Content-Type')).toBe('application/json');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should handle init headers as Headers object', () => {
|
|
29
|
+
const headers = prepareHeaders(new Headers({ init: 'foo' }), {
|
|
30
|
+
'content-type': 'application/json',
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
expect(headers.get('init')).toBe('foo');
|
|
34
|
+
expect(headers.get('Content-Type')).toBe('application/json');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should handle Response object headers', () => {
|
|
38
|
+
const initHeaders = { init: 'foo' };
|
|
39
|
+
const response = new Response(null, {
|
|
40
|
+
headers: { ...initHeaders, extra: 'bar' },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const headers = prepareHeaders(response.headers, {
|
|
44
|
+
'content-type': 'application/json',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(headers.get('init')).toBe('foo');
|
|
48
|
+
expect(headers.get('extra')).toBe('bar');
|
|
49
|
+
expect(headers.get('Content-Type')).toBe('application/json');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function prepareHeaders(
|
|
2
|
+
headers: HeadersInit | undefined,
|
|
3
|
+
defaultHeaders: Record<string, string>,
|
|
4
|
+
): Headers {
|
|
5
|
+
const responseHeaders = new Headers(headers ?? {});
|
|
6
|
+
|
|
7
|
+
for (const [key, value] of Object.entries(defaultHeaders)) {
|
|
8
|
+
if (!responseHeaders.has(key)) {
|
|
9
|
+
responseHeaders.set(key, value);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return responseHeaders;
|
|
14
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { expect, it } from 'vitest';
|
|
2
|
+
import { prepareRetries } from './prepare-retries';
|
|
3
|
+
|
|
4
|
+
it('should set default values correctly when no input is provided', () => {
|
|
5
|
+
const defaultResult = prepareRetries({
|
|
6
|
+
maxRetries: undefined,
|
|
7
|
+
abortSignal: undefined,
|
|
8
|
+
});
|
|
9
|
+
expect(defaultResult.maxRetries).toBe(2);
|
|
10
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { InvalidArgumentError } from '../error/invalid-argument-error';
|
|
2
|
+
import {
|
|
3
|
+
RetryFunction,
|
|
4
|
+
retryWithExponentialBackoffRespectingRetryHeaders,
|
|
5
|
+
} from '../util/retry-with-exponential-backoff';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Validate and prepare retries.
|
|
9
|
+
*/
|
|
10
|
+
export function prepareRetries({
|
|
11
|
+
maxRetries,
|
|
12
|
+
abortSignal,
|
|
13
|
+
}: {
|
|
14
|
+
maxRetries: number | undefined;
|
|
15
|
+
abortSignal: AbortSignal | undefined;
|
|
16
|
+
}): {
|
|
17
|
+
maxRetries: number;
|
|
18
|
+
retry: RetryFunction;
|
|
19
|
+
} {
|
|
20
|
+
if (maxRetries != null) {
|
|
21
|
+
if (!Number.isInteger(maxRetries)) {
|
|
22
|
+
throw new InvalidArgumentError({
|
|
23
|
+
parameter: 'maxRetries',
|
|
24
|
+
value: maxRetries,
|
|
25
|
+
message: 'maxRetries must be an integer',
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (maxRetries < 0) {
|
|
30
|
+
throw new InvalidArgumentError({
|
|
31
|
+
parameter: 'maxRetries',
|
|
32
|
+
value: maxRetries,
|
|
33
|
+
message: 'maxRetries must be >= 0',
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const maxRetriesResult = maxRetries ?? 2;
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
maxRetries: maxRetriesResult,
|
|
42
|
+
retry: retryWithExponentialBackoffRespectingRetryHeaders({
|
|
43
|
+
maxRetries: maxRetriesResult,
|
|
44
|
+
abortSignal,
|
|
45
|
+
}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { AISDKError } from '@ai-sdk/provider';
|
|
2
|
+
|
|
3
|
+
const name = 'AI_RetryError';
|
|
4
|
+
const marker = `vercel.ai.error.${name}`;
|
|
5
|
+
const symbol = Symbol.for(marker);
|
|
6
|
+
|
|
7
|
+
export type RetryErrorReason =
|
|
8
|
+
| 'maxRetriesExceeded'
|
|
9
|
+
| 'errorNotRetryable'
|
|
10
|
+
| 'abort';
|
|
11
|
+
|
|
12
|
+
export class RetryError extends AISDKError {
|
|
13
|
+
private readonly [symbol] = true; // used in isInstance
|
|
14
|
+
|
|
15
|
+
// note: property order determines debugging output
|
|
16
|
+
readonly reason: RetryErrorReason;
|
|
17
|
+
readonly lastError: unknown;
|
|
18
|
+
readonly errors: Array<unknown>;
|
|
19
|
+
|
|
20
|
+
constructor({
|
|
21
|
+
message,
|
|
22
|
+
reason,
|
|
23
|
+
errors,
|
|
24
|
+
}: {
|
|
25
|
+
message: string;
|
|
26
|
+
reason: RetryErrorReason;
|
|
27
|
+
errors: Array<unknown>;
|
|
28
|
+
}) {
|
|
29
|
+
super({ name, message });
|
|
30
|
+
|
|
31
|
+
this.reason = reason;
|
|
32
|
+
this.errors = errors;
|
|
33
|
+
|
|
34
|
+
// separate our last error to make debugging via log easier:
|
|
35
|
+
this.lastError = errors[errors.length - 1];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static isInstance(error: unknown): error is RetryError {
|
|
39
|
+
return AISDKError.hasMarker(error, marker);
|
|
40
|
+
}
|
|
41
|
+
}
|