@planet-matrix/mobius-model 0.5.0 → 0.9.0
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 +61 -0
- package/README.md +123 -36
- package/dist/index.js +715 -4
- package/dist/index.js.map +981 -13
- package/oxlint.config.ts +6 -0
- package/package.json +36 -18
- package/src/abort/README.md +92 -0
- package/src/abort/abort-manager.ts +278 -0
- package/src/abort/abort-signal-listener-manager.ts +81 -0
- package/src/abort/index.ts +2 -0
- package/src/ai/README.md +1 -0
- package/src/ai/ai.ts +107 -0
- package/src/ai/chat-completion-ai/aihubmix-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/chat-completion-ai.ts +270 -0
- package/src/ai/chat-completion-ai/chat-completion.ts +189 -0
- package/src/ai/chat-completion-ai/index.ts +7 -0
- package/src/ai/chat-completion-ai/lingyiwanwu-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/ohmygpt-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/openai-next-chat-completion.ts +78 -0
- package/src/ai/embedding-ai/embedding-ai.ts +63 -0
- package/src/ai/embedding-ai/embedding.ts +50 -0
- package/src/ai/embedding-ai/index.ts +4 -0
- package/src/ai/embedding-ai/openai-next-embedding.ts +23 -0
- package/src/ai/index.ts +4 -0
- package/src/aio/README.md +100 -0
- package/src/aio/content.ts +141 -0
- package/src/aio/index.ts +3 -0
- package/src/aio/json.ts +127 -0
- package/src/aio/prompt.ts +246 -0
- package/src/basic/README.md +72 -116
- package/src/basic/error.ts +19 -5
- package/src/basic/function.ts +83 -64
- package/src/basic/index.ts +1 -0
- package/src/basic/is.ts +152 -71
- package/src/basic/promise.ts +29 -8
- package/src/basic/schedule.ts +111 -0
- package/src/basic/stream.ts +135 -25
- package/src/basic/string.ts +2 -33
- package/src/color/README.md +105 -0
- package/src/color/index.ts +3 -0
- package/src/color/internal.ts +42 -0
- package/src/color/rgb/analyze.ts +236 -0
- package/src/color/rgb/construct.ts +130 -0
- package/src/color/rgb/convert.ts +227 -0
- package/src/color/rgb/derive.ts +303 -0
- package/src/color/rgb/index.ts +6 -0
- package/src/color/rgb/internal.ts +208 -0
- package/src/color/rgb/parse.ts +302 -0
- package/src/color/rgb/serialize.ts +144 -0
- package/src/color/types.ts +57 -0
- package/src/color/xyz/analyze.ts +80 -0
- package/src/color/xyz/construct.ts +19 -0
- package/src/color/xyz/convert.ts +71 -0
- package/src/color/xyz/index.ts +3 -0
- package/src/color/xyz/internal.ts +23 -0
- package/src/credential/README.md +107 -0
- package/src/credential/api-key.ts +158 -0
- package/src/credential/bearer.ts +73 -0
- package/src/credential/index.ts +4 -0
- package/src/credential/json-web-token.ts +96 -0
- package/src/credential/password.ts +170 -0
- package/src/cron/README.md +86 -0
- package/src/cron/cron.ts +87 -0
- package/src/cron/index.ts +1 -0
- package/src/css/README.md +93 -0
- package/src/css/class.ts +559 -0
- package/src/css/index.ts +1 -0
- package/src/drizzle/README.md +1 -0
- package/src/drizzle/drizzle.ts +1 -0
- package/src/drizzle/helper.ts +47 -0
- package/src/drizzle/index.ts +5 -0
- package/src/drizzle/infer.ts +52 -0
- package/src/drizzle/kysely.ts +8 -0
- package/src/drizzle/pagination.ts +200 -0
- package/src/email/README.md +1 -0
- package/src/email/index.ts +1 -0
- package/src/email/resend.ts +25 -0
- package/src/encoding/README.md +66 -79
- package/src/encoding/base64.ts +13 -4
- package/src/environment/README.md +97 -0
- package/src/environment/basic.ts +26 -0
- package/src/environment/device.ts +311 -0
- package/src/environment/feature.ts +285 -0
- package/src/environment/geo.ts +337 -0
- package/src/environment/index.ts +7 -0
- package/src/environment/runtime.ts +400 -0
- package/src/environment/snapshot.ts +60 -0
- package/src/environment/variable.ts +239 -0
- package/src/event/README.md +90 -0
- package/src/event/class-event-proxy.ts +229 -0
- package/src/event/common.ts +29 -0
- package/src/event/event-manager.ts +203 -0
- package/src/event/index.ts +4 -0
- package/src/event/instance-event-proxy.ts +187 -0
- package/src/event/internal.ts +24 -0
- package/src/exception/README.md +96 -0
- package/src/exception/browser.ts +219 -0
- package/src/exception/index.ts +4 -0
- package/src/exception/nodejs.ts +169 -0
- package/src/exception/normalize.ts +106 -0
- package/src/exception/types.ts +99 -0
- package/src/form/README.md +25 -0
- package/src/form/index.ts +1 -0
- package/src/form/inputor-controller/base.ts +874 -0
- package/src/form/inputor-controller/boolean.ts +39 -0
- package/src/form/inputor-controller/file.ts +39 -0
- package/src/form/inputor-controller/form.ts +181 -0
- package/src/form/inputor-controller/helper.ts +117 -0
- package/src/form/inputor-controller/index.ts +17 -0
- package/src/form/inputor-controller/multi-select.ts +99 -0
- package/src/form/inputor-controller/number.ts +116 -0
- package/src/form/inputor-controller/select.ts +109 -0
- package/src/form/inputor-controller/text.ts +82 -0
- package/src/http/READMD.md +1 -0
- package/src/http/api/api-core.ts +84 -0
- package/src/http/api/api-handler.ts +79 -0
- package/src/http/api/api-host.ts +47 -0
- package/src/http/api/api-result.ts +56 -0
- package/src/http/api/api-schema.ts +154 -0
- package/src/http/api/api-server.ts +130 -0
- package/src/http/api/api-test.ts +142 -0
- package/src/http/api/api-type.ts +37 -0
- package/src/http/api/api.ts +81 -0
- package/src/http/api/index.ts +11 -0
- package/src/http/api-adapter/api-core-node-http.ts +260 -0
- package/src/http/api-adapter/api-host-node-http.ts +156 -0
- package/src/http/api-adapter/api-result-arktype.ts +297 -0
- package/src/http/api-adapter/api-result-zod.ts +286 -0
- package/src/http/api-adapter/index.ts +5 -0
- package/src/http/bin/gen-api-list/gen-api-list.ts +126 -0
- package/src/http/bin/gen-api-list/index.ts +1 -0
- package/src/http/bin/gen-api-test/gen-api-test.ts +136 -0
- package/src/http/bin/gen-api-test/index.ts +1 -0
- package/src/http/bin/gen-api-type/calc-code.ts +25 -0
- package/src/http/bin/gen-api-type/gen-api-type.ts +127 -0
- package/src/http/bin/gen-api-type/index.ts +2 -0
- package/src/http/bin/index.ts +2 -0
- package/src/http/index.ts +3 -0
- package/src/huawei/README.md +1 -0
- package/src/huawei/index.ts +2 -0
- package/src/huawei/moderation/index.ts +1 -0
- package/src/huawei/moderation/moderation.ts +355 -0
- package/src/huawei/obs/esdk-obs-nodejs.d.ts +87 -0
- package/src/huawei/obs/index.ts +1 -0
- package/src/huawei/obs/obs.ts +42 -0
- package/src/identifier/README.md +92 -0
- package/src/identifier/id.ts +119 -0
- package/src/identifier/index.ts +2 -0
- package/src/identifier/uuid.ts +187 -0
- package/src/index.ts +33 -1
- package/src/json/README.md +92 -0
- package/src/json/index.ts +1 -0
- package/src/json/repair.ts +18 -0
- package/src/log/README.md +79 -0
- package/src/log/index.ts +5 -0
- package/src/log/log-emitter.ts +72 -0
- package/src/log/log-record.ts +10 -0
- package/src/log/log-scheduler.ts +74 -0
- package/src/log/log-type.ts +8 -0
- package/src/log/logger.ts +554 -0
- package/src/openai/README.md +1 -0
- package/src/openai/index.ts +1 -0
- package/src/openai/openai.ts +510 -0
- package/src/orchestration/README.md +91 -0
- package/src/orchestration/coordination/barrier.ts +214 -0
- package/src/orchestration/coordination/count-down-latch.ts +215 -0
- package/src/orchestration/coordination/errors.ts +98 -0
- package/src/orchestration/coordination/index.ts +16 -0
- package/src/orchestration/coordination/internal/wait-constraints.ts +95 -0
- package/src/orchestration/coordination/internal/wait-queue.ts +109 -0
- package/src/orchestration/coordination/keyed-lock.ts +168 -0
- package/src/orchestration/coordination/mutex.ts +257 -0
- package/src/orchestration/coordination/permit.ts +127 -0
- package/src/orchestration/coordination/read-write-lock.ts +444 -0
- package/src/orchestration/coordination/semaphore.ts +280 -0
- package/src/orchestration/dispatching/dispatcher.ts +83 -0
- package/src/orchestration/dispatching/index.ts +2 -0
- package/src/orchestration/dispatching/selector/base-selector.ts +39 -0
- package/src/orchestration/dispatching/selector/down-count-selector.ts +119 -0
- package/src/orchestration/dispatching/selector/index.ts +2 -0
- package/src/orchestration/index.ts +3 -0
- package/src/orchestration/scheduling/index.ts +2 -0
- package/src/orchestration/scheduling/scheduler.ts +103 -0
- package/src/orchestration/scheduling/task.ts +32 -0
- package/src/random/README.md +56 -86
- package/src/random/base.ts +66 -0
- package/src/random/index.ts +5 -1
- package/src/random/random-boolean.ts +40 -0
- package/src/random/random-integer.ts +60 -0
- package/src/random/random-number.ts +72 -0
- package/src/random/random-string.ts +66 -0
- package/src/reactor/README.md +4 -0
- package/src/reactor/reactor-core/primitive.ts +9 -9
- package/src/reactor/reactor-core/reactive-system.ts +5 -5
- package/src/request/README.md +108 -0
- package/src/request/fetch/base.ts +108 -0
- package/src/request/fetch/browser.ts +285 -0
- package/src/request/fetch/general.ts +20 -0
- package/src/request/fetch/index.ts +4 -0
- package/src/request/fetch/nodejs.ts +285 -0
- package/src/request/index.ts +2 -0
- package/src/request/request/base.ts +250 -0
- package/src/request/request/general.ts +64 -0
- package/src/request/request/index.ts +3 -0
- package/src/request/request/resource.ts +68 -0
- package/src/result/README.md +4 -0
- package/src/result/controller.ts +54 -0
- package/src/result/either.ts +193 -0
- package/src/result/index.ts +2 -0
- package/src/route/README.md +105 -0
- package/src/route/adapter/browser.ts +122 -0
- package/src/route/adapter/driver.ts +56 -0
- package/src/route/adapter/index.ts +2 -0
- package/src/route/index.ts +3 -0
- package/src/route/router/index.ts +2 -0
- package/src/route/router/route.ts +630 -0
- package/src/route/router/router.ts +1642 -0
- package/src/route/uri/hash.ts +308 -0
- package/src/route/uri/index.ts +7 -0
- package/src/route/uri/pathname.ts +376 -0
- package/src/route/uri/search.ts +413 -0
- package/src/singleton/README.md +79 -0
- package/src/singleton/factory.ts +55 -0
- package/src/singleton/index.ts +2 -0
- package/src/singleton/manager.ts +204 -0
- package/src/socket/README.md +105 -0
- package/src/socket/client/index.ts +2 -0
- package/src/socket/client/socket-unit.ts +660 -0
- package/src/socket/client/socket.ts +203 -0
- package/src/socket/common/index.ts +2 -0
- package/src/socket/common/socket-unit-common.ts +23 -0
- package/src/socket/common/socket-unit-heartbeat.ts +427 -0
- package/src/socket/index.ts +3 -0
- package/src/socket/server/index.ts +3 -0
- package/src/socket/server/server.ts +183 -0
- package/src/socket/server/socket-unit.ts +449 -0
- package/src/socket/server/socket.ts +264 -0
- package/src/storage/README.md +107 -0
- package/src/storage/index.ts +1 -0
- package/src/storage/table.ts +449 -0
- package/src/timer/README.md +86 -0
- package/src/timer/expiration/expiration-manager.ts +594 -0
- package/src/timer/expiration/index.ts +3 -0
- package/src/timer/expiration/min-heap.ts +208 -0
- package/src/timer/expiration/remaining-manager.ts +241 -0
- package/src/timer/index.ts +1 -0
- package/src/tube/README.md +99 -0
- package/src/tube/helper.ts +138 -0
- package/src/tube/index.ts +2 -0
- package/src/tube/tube.ts +880 -0
- package/src/type/README.md +54 -307
- package/src/type/class.ts +2 -2
- package/src/type/index.ts +14 -14
- package/src/type/is.ts +265 -2
- package/src/type/object.ts +37 -0
- package/src/type/string.ts +7 -2
- package/src/type/tuple.ts +6 -6
- package/src/type/union.ts +16 -0
- package/src/web/README.md +77 -0
- package/src/web/capture.ts +35 -0
- package/src/web/clipboard.ts +97 -0
- package/src/web/dom.ts +117 -0
- package/src/web/download.ts +16 -0
- package/src/web/event.ts +46 -0
- package/src/web/index.ts +10 -0
- package/src/web/local-storage.ts +113 -0
- package/src/web/location.ts +28 -0
- package/src/web/permission.ts +172 -0
- package/src/web/script-loader.ts +432 -0
- package/src/weixin/README.md +1 -0
- package/src/weixin/index.ts +2 -0
- package/src/weixin/official-account/authorization.ts +159 -0
- package/src/weixin/official-account/index.ts +2 -0
- package/src/weixin/official-account/js-api.ts +134 -0
- package/src/weixin/open/index.ts +1 -0
- package/src/weixin/open/oauth2.ts +133 -0
- package/tests/unit/abort/abort-manager.spec.ts +225 -0
- package/tests/unit/abort/abort-signal-listener-manager.spec.ts +62 -0
- package/tests/unit/ai/ai.spec.ts +85 -0
- package/tests/unit/aio/content.spec.ts +105 -0
- package/tests/unit/aio/json.spec.ts +147 -0
- package/tests/unit/aio/prompt.spec.ts +111 -0
- package/tests/unit/basic/array.spec.ts +1 -1
- package/tests/unit/basic/error.spec.ts +16 -4
- package/tests/unit/basic/schedule.spec.ts +74 -0
- package/tests/unit/basic/stream.spec.ts +91 -38
- package/tests/unit/basic/string.spec.ts +0 -9
- package/tests/unit/color/rgb/analyze.spec.ts +110 -0
- package/tests/unit/color/rgb/construct.spec.ts +56 -0
- package/tests/unit/color/rgb/convert.spec.ts +60 -0
- package/tests/unit/color/rgb/derive.spec.ts +103 -0
- package/tests/unit/color/rgb/parse.spec.ts +66 -0
- package/tests/unit/color/rgb/serialize.spec.ts +46 -0
- package/tests/unit/color/xyz/analyze.spec.ts +33 -0
- package/tests/unit/color/xyz/construct.spec.ts +10 -0
- package/tests/unit/color/xyz/convert.spec.ts +18 -0
- package/tests/unit/credential/api-key.spec.ts +37 -0
- package/tests/unit/credential/bearer.spec.ts +23 -0
- package/tests/unit/credential/json-web-token.spec.ts +23 -0
- package/tests/unit/credential/password.spec.ts +41 -0
- package/tests/unit/cron/cron.spec.ts +84 -0
- package/tests/unit/css/class.spec.ts +157 -0
- package/tests/unit/environment/basic.spec.ts +20 -0
- package/tests/unit/environment/device.spec.ts +146 -0
- package/tests/unit/environment/feature.spec.ts +388 -0
- package/tests/unit/environment/geo.spec.ts +111 -0
- package/tests/unit/environment/runtime.spec.ts +364 -0
- package/tests/unit/environment/snapshot.spec.ts +4 -0
- package/tests/unit/environment/variable.spec.ts +190 -0
- package/tests/unit/event/class-event-proxy.spec.ts +225 -0
- package/tests/unit/event/event-manager.spec.ts +246 -0
- package/tests/unit/event/instance-event-proxy.spec.ts +187 -0
- package/tests/unit/exception/browser.spec.ts +213 -0
- package/tests/unit/exception/nodejs.spec.ts +144 -0
- package/tests/unit/exception/normalize.spec.ts +57 -0
- package/tests/unit/form/inputor-controller/base.spec.ts +458 -0
- package/tests/unit/form/inputor-controller/boolean.spec.ts +30 -0
- package/tests/unit/form/inputor-controller/file.spec.ts +27 -0
- package/tests/unit/form/inputor-controller/form.spec.ts +120 -0
- package/tests/unit/form/inputor-controller/helper.spec.ts +67 -0
- package/tests/unit/form/inputor-controller/multi-select.spec.ts +34 -0
- package/tests/unit/form/inputor-controller/number.spec.ts +36 -0
- package/tests/unit/form/inputor-controller/select.spec.ts +49 -0
- package/tests/unit/form/inputor-controller/text.spec.ts +34 -0
- package/tests/unit/http/api/api-core-host.spec.ts +207 -0
- package/tests/unit/http/api/api-schema.spec.ts +120 -0
- package/tests/unit/http/api/api-server.spec.ts +363 -0
- package/tests/unit/http/api/api-test.spec.ts +117 -0
- package/tests/unit/http/api/api.spec.ts +121 -0
- package/tests/unit/http/api-adapter/node-http.spec.ts +191 -0
- package/tests/unit/identifier/id.spec.ts +71 -0
- package/tests/unit/identifier/uuid.spec.ts +85 -0
- package/tests/unit/json/repair.spec.ts +11 -0
- package/tests/unit/log/log-emitter.spec.ts +33 -0
- package/tests/unit/log/log-scheduler.spec.ts +40 -0
- package/tests/unit/log/log-type.spec.ts +7 -0
- package/tests/unit/log/logger.spec.ts +237 -0
- package/tests/unit/openai/openai.spec.ts +64 -0
- package/tests/unit/orchestration/coordination/barrier.spec.ts +96 -0
- package/tests/unit/orchestration/coordination/count-down-latch.spec.ts +63 -0
- package/tests/unit/orchestration/coordination/errors.spec.ts +29 -0
- package/tests/unit/orchestration/coordination/keyed-lock.spec.ts +109 -0
- package/tests/unit/orchestration/coordination/mutex.spec.ts +132 -0
- package/tests/unit/orchestration/coordination/permit.spec.ts +43 -0
- package/tests/unit/orchestration/coordination/read-write-lock.spec.ts +154 -0
- package/tests/unit/orchestration/coordination/semaphore.spec.ts +135 -0
- package/tests/unit/orchestration/dispatching/dispatcher.spec.ts +41 -0
- package/tests/unit/orchestration/dispatching/selector/down-count-selector.spec.ts +81 -0
- package/tests/unit/orchestration/scheduling/scheduler.spec.ts +103 -0
- package/tests/unit/random/base.spec.ts +58 -0
- package/tests/unit/random/random-boolean.spec.ts +25 -0
- package/tests/unit/random/random-integer.spec.ts +32 -0
- package/tests/unit/random/random-number.spec.ts +33 -0
- package/tests/unit/random/random-string.spec.ts +22 -0
- package/tests/unit/reactor/alien-signals-effect.spec.ts +11 -10
- package/tests/unit/reactor/preact-signal.spec.ts +1 -2
- package/tests/unit/request/fetch/browser.spec.ts +222 -0
- package/tests/unit/request/fetch/general.spec.ts +43 -0
- package/tests/unit/request/fetch/nodejs.spec.ts +225 -0
- package/tests/unit/request/request/base.spec.ts +385 -0
- package/tests/unit/request/request/general.spec.ts +161 -0
- package/tests/unit/route/router/route.spec.ts +431 -0
- package/tests/unit/route/router/router.spec.ts +407 -0
- package/tests/unit/route/uri/hash.spec.ts +72 -0
- package/tests/unit/route/uri/pathname.spec.ts +147 -0
- package/tests/unit/route/uri/search.spec.ts +107 -0
- package/tests/unit/singleton/singleton.spec.ts +49 -0
- package/tests/unit/socket/client.spec.ts +208 -0
- package/tests/unit/socket/server.spec.ts +135 -0
- package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
- package/tests/unit/storage/table.spec.ts +620 -0
- package/tests/unit/timer/expiration/expiration-manager.spec.ts +464 -0
- package/tests/unit/timer/expiration/min-heap.spec.ts +71 -0
- package/tests/unit/timer/expiration/remaining-manager.spec.ts +234 -0
- package/tests/unit/tube/helper.spec.ts +139 -0
- package/tests/unit/tube/tube.spec.ts +501 -0
- package/.oxlintrc.json +0 -5
- package/src/random/uuid.ts +0 -103
- package/tests/unit/random/uuid.spec.ts +0 -37
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
import type { ClientOptions } from "openai"
|
|
2
|
+
import { OpenAI } from "openai"
|
|
3
|
+
import type { Stream } from "openai/streaming"
|
|
4
|
+
import { Agent, setGlobalDispatcher } from "undici"
|
|
5
|
+
|
|
6
|
+
import type { LoggerFriendly, LoggerFriendlyOptions } from "#Source/log/index.ts"
|
|
7
|
+
import type { WithAbortSignal } from "#Source/abort/index.ts"
|
|
8
|
+
import type { Either } from "#Source/result/index.ts"
|
|
9
|
+
|
|
10
|
+
import { Logger } from "#Source/log/index.ts"
|
|
11
|
+
import * as Aio from "#Source/aio/index.ts"
|
|
12
|
+
import { controllerFromEitherType } from "#Source/result/index.ts"
|
|
13
|
+
import { Tube } from "#Source/tube/index.ts"
|
|
14
|
+
import { scheduleMacroTask, streamConsumeInMacroTask, streamTransformInMacroTask } from "#Source/basic/index.ts"
|
|
15
|
+
|
|
16
|
+
export { OpenAI } from "openai"
|
|
17
|
+
export { Stream } from "openai/streaming"
|
|
18
|
+
|
|
19
|
+
export interface WithOpenai {
|
|
20
|
+
openai: Openai
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface OriginalChatCompletionOptions
|
|
24
|
+
extends Omit<OpenAI.Chat.ChatCompletionCreateParams, never>, WithAbortSignal {
|
|
25
|
+
}
|
|
26
|
+
export type OriginalChatCompletionResult = OpenAI.Chat.ChatCompletion
|
|
27
|
+
export type OriginalChatCompletionChunkResult = OpenAI.Chat.Completions.ChatCompletionChunk
|
|
28
|
+
|
|
29
|
+
export interface CustomChatCompletionOptions extends OriginalChatCompletionOptions {
|
|
30
|
+
/**
|
|
31
|
+
* In milliseconds.
|
|
32
|
+
*/
|
|
33
|
+
timeout?: number | undefined
|
|
34
|
+
}
|
|
35
|
+
export interface CustomChatCompletionNonStreamingLeft {
|
|
36
|
+
code: "NO_CHOICE" | "TOO_LONG" | "FILTERED" | "REFUSED" | "EMPTY" | "UNKNOWN"
|
|
37
|
+
}
|
|
38
|
+
export interface CustomChatCompletionNonStreamingRight {
|
|
39
|
+
content: string
|
|
40
|
+
token: number
|
|
41
|
+
}
|
|
42
|
+
export type CustomChatCompletionNonStreamingResult
|
|
43
|
+
= Either<CustomChatCompletionNonStreamingLeft, CustomChatCompletionNonStreamingRight>
|
|
44
|
+
|
|
45
|
+
export interface CustomChatCompletionStreamingLeft {
|
|
46
|
+
code: never
|
|
47
|
+
}
|
|
48
|
+
export interface Completion {
|
|
49
|
+
content: Aio.TextContent
|
|
50
|
+
token: Aio.NumberContent
|
|
51
|
+
}
|
|
52
|
+
export interface CustomChatCompletionStreamingRight {
|
|
53
|
+
completionTube: Tube<Completion>
|
|
54
|
+
}
|
|
55
|
+
export type CustomChatCompletionStreamingResult =
|
|
56
|
+
Either<CustomChatCompletionStreamingLeft, CustomChatCompletionStreamingRight>
|
|
57
|
+
|
|
58
|
+
export interface OriginalEmbeddingOptions
|
|
59
|
+
extends OpenAI.Embeddings.EmbeddingCreateParams, WithAbortSignal {
|
|
60
|
+
}
|
|
61
|
+
export type OriginalEmbeddingResult = OpenAI.Embeddings.CreateEmbeddingResponse
|
|
62
|
+
|
|
63
|
+
export interface CustomEmbeddingOptions extends WithAbortSignal {
|
|
64
|
+
input: string
|
|
65
|
+
model?: OpenAI.Embeddings.EmbeddingCreateParams["model"] | undefined
|
|
66
|
+
dimensions?: OpenAI.Embeddings.EmbeddingCreateParams["dimensions"] | undefined
|
|
67
|
+
}
|
|
68
|
+
export interface CustomEmbeddingResultLeft {
|
|
69
|
+
code: "NO_EMBEDDING" | "MODEL_MISMATCH" | "UNKNOWN"
|
|
70
|
+
}
|
|
71
|
+
export interface CustomEmbeddingResultRight {
|
|
72
|
+
embedding: number[]
|
|
73
|
+
usage: {
|
|
74
|
+
promptTokens: number
|
|
75
|
+
totalTokens: number
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export type CustomEmbeddingResult =
|
|
79
|
+
Either<CustomEmbeddingResultLeft, CustomEmbeddingResultRight>
|
|
80
|
+
|
|
81
|
+
export type CustomSpeechOptions = Omit<OpenAI.Audio.SpeechCreateParams, "input" | "model" | "voice" | "response_format"> & {
|
|
82
|
+
input: string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface OpenaiOptions extends LoggerFriendlyOptions {
|
|
86
|
+
apiKey: Exclude<ClientOptions["apiKey"], undefined | null>
|
|
87
|
+
baseUrl: Exclude<ClientOptions["baseURL"], undefined | null>
|
|
88
|
+
/**
|
|
89
|
+
* In milliseconds.
|
|
90
|
+
*
|
|
91
|
+
* @default 5 * 60 * 1000
|
|
92
|
+
*/
|
|
93
|
+
timeout?: ClientOptions["timeout"] | undefined
|
|
94
|
+
}
|
|
95
|
+
interface ResolvedOpenaiOptions {
|
|
96
|
+
apiKey: Exclude<ClientOptions["apiKey"], undefined | null>
|
|
97
|
+
baseUrl: Exclude<ClientOptions["baseURL"], undefined | null>
|
|
98
|
+
timeout: Exclude<ClientOptions["timeout"], undefined | null>
|
|
99
|
+
}
|
|
100
|
+
export class Openai implements LoggerFriendly {
|
|
101
|
+
protected options: ResolvedOpenaiOptions
|
|
102
|
+
|
|
103
|
+
readonly logger: Logger
|
|
104
|
+
protected openai: OpenAI
|
|
105
|
+
|
|
106
|
+
constructor(options: OpenaiOptions) {
|
|
107
|
+
this.options = {
|
|
108
|
+
apiKey: options.apiKey,
|
|
109
|
+
baseUrl: options.baseUrl,
|
|
110
|
+
timeout: options.timeout ?? 5 * 60 * 1_000,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
this.logger = Logger.fromOptions(options).setDefaultName("Openai")
|
|
114
|
+
this.openai = new OpenAI({
|
|
115
|
+
baseURL: this.options.baseUrl,
|
|
116
|
+
// NOTE: 若服务方证书出现问题可暂时开启这个选项,问题解决后应该第一时间关闭。
|
|
117
|
+
// fetch: this.customFetchThatIgnoreTheCAs,
|
|
118
|
+
timeout: this.options.timeout,
|
|
119
|
+
apiKey: this.options.apiKey,
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
protected async customFetchThatIgnoreTheCAs(
|
|
124
|
+
url: string, init?: RequestInit
|
|
125
|
+
): Promise<Response> {
|
|
126
|
+
setGlobalDispatcher(new Agent({ connect: { rejectUnauthorized: false } }))
|
|
127
|
+
|
|
128
|
+
const response = await fetch(url, init)
|
|
129
|
+
if (response.ok === false) {
|
|
130
|
+
throw new Error(`Request failed with status ${response.status}`)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return response
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
getBaseUrl(): string {
|
|
137
|
+
return this.openai.baseURL
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 原始 chatCompletion 方法的包装,但将其固定为非流式且不自动重试。
|
|
142
|
+
*/
|
|
143
|
+
async originalChatCompletionNonStreaming(
|
|
144
|
+
options: OriginalChatCompletionOptions,
|
|
145
|
+
): Promise<OriginalChatCompletionResult> {
|
|
146
|
+
try {
|
|
147
|
+
const { abortSignal, ...restOptions } = options
|
|
148
|
+
const response = await this.openai.chat.completions.create({
|
|
149
|
+
...restOptions,
|
|
150
|
+
stream: false,
|
|
151
|
+
}, {
|
|
152
|
+
maxRetries: 0,
|
|
153
|
+
signal: abortSignal,
|
|
154
|
+
})
|
|
155
|
+
return response
|
|
156
|
+
}
|
|
157
|
+
catch (exception) {
|
|
158
|
+
this.logger.error("Error creating chat completion: ", exception)
|
|
159
|
+
throw exception
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 原始 chatCompletion 方法的包装,但将其固定为流式且不自动重试。
|
|
165
|
+
*/
|
|
166
|
+
async originalChatCompletionStreaming(
|
|
167
|
+
options: OriginalChatCompletionOptions,
|
|
168
|
+
): Promise<Stream<OriginalChatCompletionChunkResult>> {
|
|
169
|
+
try {
|
|
170
|
+
const { abortSignal, ...restOptions } = options
|
|
171
|
+
const response = await this.openai.chat.completions.create({
|
|
172
|
+
...restOptions,
|
|
173
|
+
stream: true,
|
|
174
|
+
}, {
|
|
175
|
+
maxRetries: 0,
|
|
176
|
+
signal: abortSignal,
|
|
177
|
+
})
|
|
178
|
+
return response
|
|
179
|
+
}
|
|
180
|
+
catch (exception) {
|
|
181
|
+
this.logger.error("Error creating chat completion: ", exception)
|
|
182
|
+
throw exception
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 此方法不会自动重试,调用方需要通过错误信息自行处理。
|
|
188
|
+
*
|
|
189
|
+
* {@link https://platform.openai.com/docs/api-reference/chat | Chat - OpenAI API}
|
|
190
|
+
*/
|
|
191
|
+
async customChatCompletionNonStreaming(
|
|
192
|
+
options: CustomChatCompletionOptions,
|
|
193
|
+
): Promise<CustomChatCompletionNonStreamingResult> {
|
|
194
|
+
const controller = controllerFromEitherType<CustomChatCompletionNonStreamingResult>()
|
|
195
|
+
try {
|
|
196
|
+
const { abortSignal, ...restOptions } = options
|
|
197
|
+
const response = await this.openai.chat.completions.create({
|
|
198
|
+
...restOptions,
|
|
199
|
+
stream: false,
|
|
200
|
+
}, {
|
|
201
|
+
maxRetries: 0,
|
|
202
|
+
signal: abortSignal,
|
|
203
|
+
})
|
|
204
|
+
const choice = response.choices[0]
|
|
205
|
+
if (choice === undefined) {
|
|
206
|
+
this.logger.error("Chat completion is empty.")
|
|
207
|
+
return await controller.returnLeft({ code: "NO_CHOICE" })
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const finishReason = choice.finish_reason
|
|
211
|
+
if (finishReason === "length") {
|
|
212
|
+
this.logger.error("Chat completion is too long.")
|
|
213
|
+
return await controller.returnLeft({ code: "TOO_LONG" })
|
|
214
|
+
}
|
|
215
|
+
if (finishReason === "content_filter") {
|
|
216
|
+
this.logger.error("Chat completion is filtered.")
|
|
217
|
+
return await controller.returnLeft({ code: "FILTERED" })
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const refusal = choice.message.refusal
|
|
221
|
+
if (refusal !== null && refusal !== undefined) {
|
|
222
|
+
this.logger.error(`Chat completion is refused: ${refusal}`)
|
|
223
|
+
return await controller.returnLeft({ code: "REFUSED" })
|
|
224
|
+
}
|
|
225
|
+
const content = choice.message.content
|
|
226
|
+
if (content === null) {
|
|
227
|
+
this.logger.error("Chat completion is empty.")
|
|
228
|
+
return await controller.returnLeft({ code: "EMPTY" })
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return await controller.returnRight({
|
|
232
|
+
content,
|
|
233
|
+
// TODO: count the tokens
|
|
234
|
+
token: 0,
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
catch (exception) {
|
|
238
|
+
this.logger.error("Error creating chat completion: ", exception)
|
|
239
|
+
return await controller.returnLeft({ code: "UNKNOWN" })
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async customChatCompletionStreaming(
|
|
244
|
+
options: CustomChatCompletionOptions,
|
|
245
|
+
): Promise<CustomChatCompletionStreamingResult> {
|
|
246
|
+
const controller = controllerFromEitherType<CustomChatCompletionStreamingResult>()
|
|
247
|
+
|
|
248
|
+
const {
|
|
249
|
+
timeout = this.options.timeout,
|
|
250
|
+
abortSignal,
|
|
251
|
+
} = options
|
|
252
|
+
const openaiClient = this.openai
|
|
253
|
+
const logger = this.logger
|
|
254
|
+
|
|
255
|
+
const completionTube = new Tube<Completion>({
|
|
256
|
+
historyCount: Infinity,
|
|
257
|
+
replayHistory: true
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
const start = async (): Promise<void> => {
|
|
261
|
+
const { abortSignal: _, ...restOptions } = options
|
|
262
|
+
try {
|
|
263
|
+
// Pass the AbortSignal to the OpenAI request to support aborting
|
|
264
|
+
logger.log("Starting chat completion streaming.")
|
|
265
|
+
const response = await openaiClient.chat.completions.create(
|
|
266
|
+
{
|
|
267
|
+
...restOptions,
|
|
268
|
+
stream: true,
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
timeout,
|
|
272
|
+
maxRetries: 0,
|
|
273
|
+
signal: abortSignal, // Include the abort signal in the request options
|
|
274
|
+
},
|
|
275
|
+
)
|
|
276
|
+
logger.log("Chat completion streaming started.")
|
|
277
|
+
|
|
278
|
+
// NOTE: 以下将 response 转换成 ReadableStream
|
|
279
|
+
const readableStream = streamTransformInMacroTask<Uint8Array, OriginalChatCompletionChunkResult>({
|
|
280
|
+
reader: response.toReadableStream().getReader(),
|
|
281
|
+
onChunk: (chunk, controller) => {
|
|
282
|
+
if (abortSignal !== undefined && abortSignal.aborted) {
|
|
283
|
+
logger.log("Original chat completion api's stream aborted by invoker.")
|
|
284
|
+
const error = new Error("Original chat completion api's stream aborted by invoker.")
|
|
285
|
+
controller.error(error)
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
if (chunk.done) {
|
|
289
|
+
logger.log("Original chat completion api's stream ended.")
|
|
290
|
+
controller.close()
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
const textDecoder = new TextDecoder()
|
|
294
|
+
const decoded = textDecoder.decode(chunk.value)
|
|
295
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
296
|
+
const parsed = JSON.parse(decoded) as OriginalChatCompletionChunkResult
|
|
297
|
+
// logger.log("Original chat completion api's stream chunk:", JSON.stringify(parsed))
|
|
298
|
+
controller.enqueue(parsed)
|
|
299
|
+
return
|
|
300
|
+
},
|
|
301
|
+
onError: (error) => {
|
|
302
|
+
logger.error("Error reading original chat completion api's stream:", error)
|
|
303
|
+
},
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
const latestCompletion: Completion = {
|
|
307
|
+
content: {
|
|
308
|
+
deltaList: [],
|
|
309
|
+
total: "",
|
|
310
|
+
},
|
|
311
|
+
token: {
|
|
312
|
+
deltaList: [],
|
|
313
|
+
total: 0,
|
|
314
|
+
},
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
streamConsumeInMacroTask({
|
|
318
|
+
readableStream,
|
|
319
|
+
onValue: async (chunk) => {
|
|
320
|
+
// If the signal is aborted, break and stop the stream
|
|
321
|
+
if (abortSignal !== undefined && abortSignal.aborted) {
|
|
322
|
+
throw new Error("Stream aborted by abort signal")
|
|
323
|
+
}
|
|
324
|
+
const choice = chunk.choices[0]
|
|
325
|
+
if (choice === undefined) {
|
|
326
|
+
// NOTE: sometimes there will be no choice in the chunk, just skip the chunk
|
|
327
|
+
// Example chunk:
|
|
328
|
+
// {
|
|
329
|
+
// "id": "chatcmpl-hTMAcJUVRpO5Etj64tdowhgpQalcP",
|
|
330
|
+
// "object": "chat.completion.chunk",
|
|
331
|
+
// "created": 1734481734,
|
|
332
|
+
// "model": "gpt-4o-mini",
|
|
333
|
+
// "choices": [],
|
|
334
|
+
// "usage": {
|
|
335
|
+
// "prompt_tokens": 2345,
|
|
336
|
+
// "completion_tokens": 90,
|
|
337
|
+
// "total_tokens": 2435,
|
|
338
|
+
// "prompt_tokens_details": {
|
|
339
|
+
// "cached_tokens": 0,
|
|
340
|
+
// "audio_tokens": 0
|
|
341
|
+
// },
|
|
342
|
+
// "completion_tokens_details": {
|
|
343
|
+
// "audio_tokens": 0
|
|
344
|
+
// }
|
|
345
|
+
// }
|
|
346
|
+
// }
|
|
347
|
+
return
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const finishReason = choice.finish_reason
|
|
351
|
+
if (finishReason === "stop") {
|
|
352
|
+
// the model hit a natural stop point or a provided stop sequence, do nothing
|
|
353
|
+
}
|
|
354
|
+
if (finishReason === "length") {
|
|
355
|
+
// the maximum number of tokens specified in the request was reached
|
|
356
|
+
logger.error("Chat completion is too long.")
|
|
357
|
+
}
|
|
358
|
+
if (finishReason === "content_filter") {
|
|
359
|
+
logger.error("Chat completion is filtered.")
|
|
360
|
+
throw new Error("Chat completion is filtered.")
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const refusal = choice.delta.refusal
|
|
364
|
+
if (refusal !== null && refusal !== undefined) {
|
|
365
|
+
logger.error(`Chat completion is refused: ${refusal}`)
|
|
366
|
+
throw new Error(`Chat completion is refused: ${refusal}`)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const deltaContent = choice.delta.content
|
|
370
|
+
if (deltaContent !== null && deltaContent !== undefined) {
|
|
371
|
+
latestCompletion.content = Aio.applyDeltaToTextContent(latestCompletion.content, {
|
|
372
|
+
type: "append",
|
|
373
|
+
text: deltaContent,
|
|
374
|
+
})
|
|
375
|
+
latestCompletion.token = Aio.applyDeltaToNumberContent(latestCompletion.token, {
|
|
376
|
+
type: "append",
|
|
377
|
+
value: 0,
|
|
378
|
+
})
|
|
379
|
+
await completionTube.pushData(structuredClone(latestCompletion))
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
onDone: async () => {
|
|
383
|
+
await completionTube.end()
|
|
384
|
+
},
|
|
385
|
+
onError: async (error) => {
|
|
386
|
+
await completionTube.pushError(error)
|
|
387
|
+
},
|
|
388
|
+
})
|
|
389
|
+
}
|
|
390
|
+
catch (exception) {
|
|
391
|
+
if (abortSignal !== undefined && abortSignal.aborted) {
|
|
392
|
+
logger.log("Original chat completion api's stream aborted by invoker.")
|
|
393
|
+
const error = new Error("Original chat completion api's stream aborted by invoker.")
|
|
394
|
+
await completionTube.pushError(error)
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
logger.error("Error starting chat completion streaming: ", exception)
|
|
398
|
+
const error = new Error(`Error starting chat completion streaming, ${String(exception)}`)
|
|
399
|
+
await completionTube.pushError(error)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
scheduleMacroTask({
|
|
404
|
+
task: async () => {
|
|
405
|
+
await start()
|
|
406
|
+
},
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
return await controller.returnRight({
|
|
410
|
+
completionTube,
|
|
411
|
+
})
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* 原始 embedding 方法的包装,将其最大重试次数固定为 0。
|
|
416
|
+
*/
|
|
417
|
+
async originalEmbedding(
|
|
418
|
+
options: OriginalEmbeddingOptions
|
|
419
|
+
): Promise<OriginalEmbeddingResult> {
|
|
420
|
+
const { abortSignal, ...restOptions } = options
|
|
421
|
+
try {
|
|
422
|
+
const response = await this.openai.embeddings.create({
|
|
423
|
+
...restOptions,
|
|
424
|
+
}, {
|
|
425
|
+
maxRetries: 0,
|
|
426
|
+
signal: abortSignal,
|
|
427
|
+
})
|
|
428
|
+
return response
|
|
429
|
+
}
|
|
430
|
+
catch (exception) {
|
|
431
|
+
this.logger.error("Error creating embedding: ", exception)
|
|
432
|
+
throw exception
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* {@link https://platform.openai.com/docs/api-reference/embeddings | Embeddings - OpenAI API}
|
|
438
|
+
*/
|
|
439
|
+
async customEmbedding(
|
|
440
|
+
options: CustomEmbeddingOptions
|
|
441
|
+
): Promise<CustomEmbeddingResult> {
|
|
442
|
+
const controller = controllerFromEitherType<CustomEmbeddingResult>()
|
|
443
|
+
const logger = this.logger
|
|
444
|
+
try {
|
|
445
|
+
const {
|
|
446
|
+
abortSignal,
|
|
447
|
+
input,
|
|
448
|
+
model = "text-embedding-3-large",
|
|
449
|
+
// IMPORTANT: the number of dimensions should be 1536 currently, ask @kongxiangyan for more information.
|
|
450
|
+
dimensions = 1_536,
|
|
451
|
+
} = options
|
|
452
|
+
logger.log(`Creating embedding with model: ${model}, dimensions: ${dimensions}, input: ${input}`)
|
|
453
|
+
const response = await this.openai.embeddings.create({
|
|
454
|
+
input,
|
|
455
|
+
model,
|
|
456
|
+
dimensions,
|
|
457
|
+
}, {
|
|
458
|
+
maxRetries: 0,
|
|
459
|
+
signal: abortSignal,
|
|
460
|
+
})
|
|
461
|
+
logger.log("Get embedding response.")
|
|
462
|
+
// logger.log("Embedding response: ", JSON.stringify(response, null, 2))
|
|
463
|
+
if (response.data.length === 0) {
|
|
464
|
+
logger.error("Embedding is empty.")
|
|
465
|
+
return await controller.returnLeft({ code: "NO_EMBEDDING" })
|
|
466
|
+
}
|
|
467
|
+
if (response.model !== model) {
|
|
468
|
+
logger.error(`Model mismatch: ${response.model} !== ${model}`)
|
|
469
|
+
return await controller.returnLeft({ code: "MODEL_MISMATCH" })
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const embedding = response.data[0]!.embedding
|
|
473
|
+
const promptTokens = response.usage.prompt_tokens
|
|
474
|
+
const totalTokens = response.usage.total_tokens
|
|
475
|
+
return await controller.returnRight({
|
|
476
|
+
embedding,
|
|
477
|
+
usage: {
|
|
478
|
+
promptTokens,
|
|
479
|
+
totalTokens,
|
|
480
|
+
},
|
|
481
|
+
})
|
|
482
|
+
}
|
|
483
|
+
catch (exception) {
|
|
484
|
+
logger.error("Error creating embedding: ", exception)
|
|
485
|
+
return await controller.returnLeft({ code: "UNKNOWN" })
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* {@link https://platform.openai.com/docs/api-reference/audio/createSpeech | Audio - OpenAI API}
|
|
491
|
+
*/
|
|
492
|
+
async customSpeech(options: CustomSpeechOptions, stream?: boolean): Promise<Buffer> {
|
|
493
|
+
try {
|
|
494
|
+
const response = await this.openai.audio.speech.create({
|
|
495
|
+
...options,
|
|
496
|
+
model: "tts-1",
|
|
497
|
+
voice: "alloy",
|
|
498
|
+
response_format: "opus",
|
|
499
|
+
}, {
|
|
500
|
+
stream: stream ?? false,
|
|
501
|
+
})
|
|
502
|
+
const buffer = Buffer.from(await response.arrayBuffer())
|
|
503
|
+
return buffer
|
|
504
|
+
}
|
|
505
|
+
catch (exception) {
|
|
506
|
+
this.logger.error("Error creating speech: ", exception)
|
|
507
|
+
throw exception
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Orchestration
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
Orchestration 模块提供围绕执行单元编排(orchestration)的基础建模能力,用于把多个执行单元之间的创建、组织、调度、分派、等待、放行、推进、收束以及整体协作关系表达为稳定且可复用的公共语义。
|
|
6
|
+
|
|
7
|
+
它关注的不是某个具体框架、某个具体队列系统或某个具体工作流引擎的宿主接口,而是“多个执行单元如何被组织为可理解、可组合、可控制、可持续演化的编排结构”这一更一般的问题。因此,这个模块不应被理解为仅仅承载互斥锁、信号量或栅栏的协调原语集合;这些内容只是当前实现中更基础的一层,而不是 Orchestration 这个词所指向的全部问题域。
|
|
8
|
+
|
|
9
|
+
## For Understanding
|
|
10
|
+
|
|
11
|
+
理解 Orchestration 模块时,应先把它看作“执行单元组织关系”的建模层,而不是一组彼此孤立的小工具。它要解决的问题,不只是多个异步流程怎样竞争资源、怎样等待同步点,也包括执行单元该如何被表达、如何被组合成更大的结构、流程阶段该如何被推进,以及这些结构之间应该暴露什么样的稳定公共语义。
|
|
12
|
+
|
|
13
|
+
因此,Orchestration 更适合被理解为一个分层的问题域:较底层是 coordination,用于表达进入控制、等待约束、同步点和资源占用;在其之上还可以有 dispatching,用于表达一组候选执行目标如何被选择、如何按上下文形成选择视图,以及反馈如何影响后续分派;再往上则可以逐步生长出更接近编排结构本身的能力,用于表达单个执行单元、执行单元之间的关系、阶段推进、依赖收敛、生命周期以及整体执行边界。未来这些能力的形态可能会落在 task、job、workflow 或其它更合适的抽象上,但 coordination 与 dispatching 都只是这些能力可复用的基础层,不等同于整个 orchestration 模块。
|
|
14
|
+
|
|
15
|
+
理解这个模块时,还应守住几条边界原则:
|
|
16
|
+
|
|
17
|
+
- Orchestration 模块表达的是编排语义与协调语义,但不应直接承担某个业务系统专属的流程定义语言、运营规则或领域状态机。
|
|
18
|
+
- 它可以吸收一切稳定且通用的编排概念,但这些概念应以模型形式出现,而不是某个具体平台配置格式、DSL 或产品术语的直接翻版。
|
|
19
|
+
- coordination 适合表达进程内、运行时内的基础协作约束;更高层的编排能力即使依赖这些约束,也不应把自己退化成底层锁语义的别名。
|
|
20
|
+
- dispatching 适合表达一组候选目标之间的运行时分派、反馈驱动选择与局部选择视图,但不应直接退化成只面向某类网络节点的负载均衡器别名。
|
|
21
|
+
- 它可以为重试、取消、阶段推进、依赖关系或执行计划等语义提供抽象空间,但不应把监控、持久化、分布式共识、租约续期或平台运维策略直接混入核心公共 API。
|
|
22
|
+
|
|
23
|
+
## For Using
|
|
24
|
+
|
|
25
|
+
当你希望把一组执行动作组织成清楚、可维护、可组合的编排结构,而不是在业务代码中反复拼接 `Promise`、计数器、布尔状态、临时队列和零散流程控制逻辑时,可以使用 Orchestration 模块。
|
|
26
|
+
|
|
27
|
+
从使用角度看,当前模块中的能力大致可以分为三类:
|
|
28
|
+
|
|
29
|
+
- 底层协调能力:用于表达互斥进入、限量并发、阶段会合、闭锁等待、资源占用句柄以及超时和中止等等待约束。
|
|
30
|
+
- 分派能力:用于表达多个候选执行目标之间如何被选择、如何基于调用上下文形成可复用的 selector,以及失败或恢复反馈如何影响后续选择。
|
|
31
|
+
- 更高层编排能力:用于表达单个执行单元的边界、多个执行单元之间的关系、阶段推进、失败传播、取消语义、执行计划以及整体完成条件。这部分目前还不是主实现重心,但属于该模块未来合理的组成方向。
|
|
32
|
+
- 结构化执行能力:用于把一组原本零散的异步动作收束为可以理解、组合与扩展的执行结构。未来它可能体现为 task、job、workflow,也可能体现为其它更贴切的抽象,但都不应退化成业务代码里对底层 Promise 的随意组合。
|
|
33
|
+
|
|
34
|
+
更合适的接入方式,是先判断你的问题属于哪一层:如果你的核心诉求是某个时刻能否进入、是否需要排队、何时统一继续,那么你更接近 coordination;如果你的核心诉求已经开始涉及执行单元如何被声明、取消、组合、计划、收束或被纳入更大的执行结构,那么你已经进入更高层的 orchestration 语义。
|
|
35
|
+
|
|
36
|
+
当前对外已经落地的主要是 `coordination` 与 `dispatching` 子模块,因此现阶段使用时既可以处理互斥、信号量、读写锁、闭锁与栅栏这类问题,也可以处理一组候选目标之间的 selector 构造、目标选择与反馈驱动分派;但理解这个模块时仍不应把它局限为“并发锁工具箱”或“负载均衡工具箱”。更合适的看法是:coordination 与 dispatching 先提供底层编排积木,而 orchestration 的整体职责会继续向更完整的执行组织与流程编排语义扩展。
|
|
37
|
+
|
|
38
|
+
## For Contributing
|
|
39
|
+
|
|
40
|
+
为 Orchestration 模块贡献内容时,优先判断新增能力是否真的在澄清某种稳定的编排语义,而不是只是在补一个局部业务流程的便捷封装。这个模块应长期服务于“执行单元如何被表达”“多个执行单元如何组成更大的结构”“流程如何推进与结束”“等待与放行如何成为可复用原语”这几类问题。如果一个能力只有在绑定特定平台、特定产品流程或特定部署环境时才成立,那么它通常不应直接成为该模块的公共组成部分。
|
|
41
|
+
|
|
42
|
+
扩展这个模块时,应特别警惕以下倾向:把 orchestration 退化成一堆零散锁工具;把 dispatching 退化成只对某一类 upstream 节点成立的网络负载均衡器包装;把某个具体工作流引擎的配置模型直接照搬进公共 API;把重试、补偿、优先级、监控或持久化策略无差别地下沉到所有层级;把调用方的一次性业务约定固化为模块语义;或者为了某个实现细节而暴露内部等待队列、内部状态节点、候选集快照与临时调度结构。文档应说明哪些编排边界是长期成立的,以及为什么这些边界成立,而不是复述当前版本的局部实现技巧。
|
|
43
|
+
|
|
44
|
+
### JSDoc 注释格式要求
|
|
45
|
+
|
|
46
|
+
- 每个公开导出的目标(类型、函数、变量、类等)都应包含 JSDoc 注释,让人在不跳转实现的情况下就能理解用途。
|
|
47
|
+
- JSDoc 注释第一行应为清晰且简洁的描述,该描述优先使用中文(英文也可以)。
|
|
48
|
+
- 如果描述后还有其他内容,应在描述后加一个空行。
|
|
49
|
+
- 如果有示例,应使用 `@example` 标签,后接三重反引号代码块(不带语言标识)。
|
|
50
|
+
- 如果有示例,应包含多个场景,展示不同用法,尤其要覆盖常见组合方式或边界输入。
|
|
51
|
+
- 如果有示例,应使用注释格式说明每个场景:`// Expect: <result>`。
|
|
52
|
+
- 如果有示例,应将结果赋值给 `example1`、`example2` 之类的变量,以保持示例易读。
|
|
53
|
+
- 如果有示例,`// Expect: <result>` 应该位于 `example1`、`example2` 之前,以保持示例的逻辑清晰。
|
|
54
|
+
- 如果有示例,应优先使用确定性示例;避免断言精确的随机输出。
|
|
55
|
+
- 如果函数返回结构化字符串,应展示其预期格式特征。
|
|
56
|
+
- 如果有参考资料,应将 `@see` 放在 `@example` 代码块之后,并用一个空行分隔。
|
|
57
|
+
|
|
58
|
+
### 实现规范要求
|
|
59
|
+
|
|
60
|
+
- 不同程序元素之间使用一个空行分隔,保持结构清楚。这里的程序元素,通常指函数、类型、常量,以及直接服务于它们的辅助元素。
|
|
61
|
+
- 某程序元素独占的辅助元素与该程序元素本身视为一个整体,不要在它们之间添加空行。
|
|
62
|
+
- 程序元素的辅助元素应该放置在该程序元素的上方,以保持阅读时的逻辑顺序。
|
|
63
|
+
- 若辅助元素被多个程序元素共享,则应将其视为独立的程序元素,放在这些程序元素中第一个相关目标的上方,并与后续程序元素之间保留一个空行。
|
|
64
|
+
- 辅助元素也应该像其它程序元素一样,保持清晰的命名和适当的注释,以便在需要阅读实现细节时能够快速理解它们的作用和使用方式。
|
|
65
|
+
- 辅助元素的命名必须以前缀 `internal` 开头(或 `Internal`,大小写不敏感)。
|
|
66
|
+
- 辅助元素永远不要公开导出。
|
|
67
|
+
- 被模块内多个不同文件中的程序元素共享的辅助元素,应该放在一个单独的文件中,例如 `./src/orchestration/internal.ts`。
|
|
68
|
+
- 模块内可以包含子模块。只有当某个子目录表达一个稳定、可单独理解、且可能被父模块重导出的子问题域时,才应将其视为子模块。
|
|
69
|
+
- 子模块包含多个文件时,应该为其单独创建子文件夹,并为其创建单独的 Barrel 文件;父模块的 Barrel 文件再重导出子模块的 Barrel 文件。
|
|
70
|
+
- 子模块不需要有自己的 `README.md`。
|
|
71
|
+
- 子模块可以有自己的 `internal.ts` 文件,多个子模块共享的辅助元素应该放在父模块的 `internal.ts` 文件中,单个子模块共享的辅助元素应该放在该子模块的 `internal.ts` 文件中。
|
|
72
|
+
- 对模块依赖关系的要求(通常是不循环依赖或不反向依赖)与对 DRY 的要求可能产生冲突。此时,若复用的代码数量不大,可以适当牺牲 DRY,复制粘贴并保留必要的注释说明;若复用的代码数量较大,则可以将其抽象到新的文件或子模块中,如 `common.ts`,并在需要的地方导入使用。
|
|
73
|
+
- 与 orchestration 相关的实现应优先围绕执行单元边界、执行单元关系、等待约束、分派语义、生命周期推进、阶段语义与可组合性组织;其中 coordination 子模块再单独关注占用句柄、队列调度、公平性与同步阶段语义,dispatching 子模块再单独关注候选目标集合、selector 视图、选择策略与反馈驱动调整,避免把具体业务流程、网络拓扑假设或宿主运行时策略混入模块边界。
|
|
74
|
+
|
|
75
|
+
### 导出策略要求
|
|
76
|
+
|
|
77
|
+
- 保持内部辅助项和内部符号为私有,不要让外部接入依赖临时性的内部结构。
|
|
78
|
+
- 每个模块都应有一个用于重导出所有公共 API 的 Barrel 文件。
|
|
79
|
+
- Barrel 文件应命名为 `index.ts`,放在模块目录根部,并且所有公共 API 都应从该文件导出。
|
|
80
|
+
- 新增公共能力时,应优先检查它是否表达稳定、清楚且值得长期维护的编排语义、协调语义或分派语义,而不是某段实现细节的便捷暴露;仅在确认需要长期对外承诺时再加入 Barrel 导出。
|
|
81
|
+
|
|
82
|
+
### 测试要求
|
|
83
|
+
|
|
84
|
+
- 若程序元素是函数,则只为该函数编写一个测试,如果该函数需要测试多个用例,应放在同一个测试中。
|
|
85
|
+
- 若程序元素是类,则至少要为该类的每一个方法编写一个测试,如果该方法需要测试多个用例,应放在同一个测试中。
|
|
86
|
+
- 若程序元素是类,除了为该类的每一个方法编写至少一个测试之外,还可以为该类编写任意多个测试,以覆盖该类的不同使用场景或边界情况。
|
|
87
|
+
- 若编写测试时需要用到辅助元素(Mock 或 Spy 等),可以在测试文件中直接定义这些辅助元素。若辅助元素较为简单,则可以直接放在每一个测试内部,优先保证每个测试的独立性,而不是追求极致 DRY;若辅助元素较为复杂或需要在多个测试中复用,则可以放在测试文件顶部,供该测试文件中的所有测试使用。
|
|
88
|
+
- 测试顺序应与源文件中被测试目标的原始顺序保持一致。
|
|
89
|
+
- 若该模块不需要测试,必须在说明文件中明确说明该模块不需要测试,并说明理由。一般来说,只有在该模块没有可执行的公共函数、只承载类型层表达,或其语义已被上层模块的测试完整覆盖且重复测试几乎不再带来额外价值时,才适合这样处理。
|
|
90
|
+
- 模块的单元测试文件目录是 `./tests/unit/orchestration`,若模块包含子模块,则子模块的单元测试文件目录为 `./tests/unit/orchestration/<sub-module-name>`。
|
|
91
|
+
- 对这个模块来说,测试应按层覆盖:coordination 层优先覆盖资源获取与释放、等待队列的 FIFO 或公平性约束、超时与中止导致的失败语义、同步点完成或破坏时的整体行为,以及 permit 重复释放等误用路径;dispatching 层优先覆盖 selector 复用与销毁、候选目标过滤、策略驱动的选择顺序、无可选目标与无可用目标的区分,以及反馈回写后对后续选择的影响;未来若引入更高层编排能力,则应优先覆盖生命周期推进、依赖关系收敛、失败传播、取消与重试边界,以及整体编排结果的确定性。
|