@planet-matrix/mobius-model 0.6.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.
Files changed (233) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/index.js +706 -36
  3. package/dist/index.js.map +855 -59
  4. package/package.json +28 -16
  5. package/src/ai/README.md +1 -0
  6. package/src/ai/ai.ts +107 -0
  7. package/src/ai/chat-completion-ai/aihubmix-chat-completion.ts +78 -0
  8. package/src/ai/chat-completion-ai/chat-completion-ai.ts +270 -0
  9. package/src/ai/chat-completion-ai/chat-completion.ts +189 -0
  10. package/src/ai/chat-completion-ai/index.ts +7 -0
  11. package/src/ai/chat-completion-ai/lingyiwanwu-chat-completion.ts +78 -0
  12. package/src/ai/chat-completion-ai/ohmygpt-chat-completion.ts +78 -0
  13. package/src/ai/chat-completion-ai/openai-next-chat-completion.ts +78 -0
  14. package/src/ai/embedding-ai/embedding-ai.ts +63 -0
  15. package/src/ai/embedding-ai/embedding.ts +50 -0
  16. package/src/ai/embedding-ai/index.ts +4 -0
  17. package/src/ai/embedding-ai/openai-next-embedding.ts +23 -0
  18. package/src/ai/index.ts +4 -0
  19. package/src/aio/README.md +100 -0
  20. package/src/aio/content.ts +141 -0
  21. package/src/aio/index.ts +3 -0
  22. package/src/aio/json.ts +127 -0
  23. package/src/aio/prompt.ts +246 -0
  24. package/src/basic/README.md +20 -15
  25. package/src/basic/error.ts +19 -5
  26. package/src/basic/function.ts +2 -2
  27. package/src/basic/index.ts +1 -0
  28. package/src/basic/schedule.ts +111 -0
  29. package/src/basic/stream.ts +135 -25
  30. package/src/credential/README.md +107 -0
  31. package/src/credential/api-key.ts +158 -0
  32. package/src/credential/bearer.ts +73 -0
  33. package/src/credential/index.ts +4 -0
  34. package/src/credential/json-web-token.ts +96 -0
  35. package/src/credential/password.ts +170 -0
  36. package/src/cron/README.md +86 -0
  37. package/src/cron/cron.ts +87 -0
  38. package/src/cron/index.ts +1 -0
  39. package/src/drizzle/README.md +1 -0
  40. package/src/drizzle/drizzle.ts +1 -0
  41. package/src/drizzle/helper.ts +47 -0
  42. package/src/drizzle/index.ts +5 -0
  43. package/src/drizzle/infer.ts +52 -0
  44. package/src/drizzle/kysely.ts +8 -0
  45. package/src/drizzle/pagination.ts +200 -0
  46. package/src/email/README.md +1 -0
  47. package/src/email/index.ts +1 -0
  48. package/src/email/resend.ts +25 -0
  49. package/src/event/class-event-proxy.ts +6 -5
  50. package/src/event/common.ts +13 -3
  51. package/src/event/event-manager.ts +3 -3
  52. package/src/event/instance-event-proxy.ts +6 -5
  53. package/src/event/internal.ts +4 -4
  54. package/src/form/README.md +25 -0
  55. package/src/form/index.ts +1 -0
  56. package/src/form/inputor-controller/base.ts +874 -0
  57. package/src/form/inputor-controller/boolean.ts +39 -0
  58. package/src/form/inputor-controller/file.ts +39 -0
  59. package/src/form/inputor-controller/form.ts +181 -0
  60. package/src/form/inputor-controller/helper.ts +117 -0
  61. package/src/form/inputor-controller/index.ts +17 -0
  62. package/src/form/inputor-controller/multi-select.ts +99 -0
  63. package/src/form/inputor-controller/number.ts +116 -0
  64. package/src/form/inputor-controller/select.ts +109 -0
  65. package/src/form/inputor-controller/text.ts +82 -0
  66. package/src/http/READMD.md +1 -0
  67. package/src/http/api/api-core.ts +84 -0
  68. package/src/http/api/api-handler.ts +79 -0
  69. package/src/http/api/api-host.ts +47 -0
  70. package/src/http/api/api-result.ts +56 -0
  71. package/src/http/api/api-schema.ts +154 -0
  72. package/src/http/api/api-server.ts +130 -0
  73. package/src/http/api/api-test.ts +142 -0
  74. package/src/http/api/api-type.ts +37 -0
  75. package/src/http/api/api.ts +81 -0
  76. package/src/http/api/index.ts +11 -0
  77. package/src/http/api-adapter/api-core-node-http.ts +260 -0
  78. package/src/http/api-adapter/api-host-node-http.ts +156 -0
  79. package/src/http/api-adapter/api-result-arktype.ts +297 -0
  80. package/src/http/api-adapter/api-result-zod.ts +286 -0
  81. package/src/http/api-adapter/index.ts +5 -0
  82. package/src/http/bin/gen-api-list/gen-api-list.ts +126 -0
  83. package/src/http/bin/gen-api-list/index.ts +1 -0
  84. package/src/http/bin/gen-api-test/gen-api-test.ts +136 -0
  85. package/src/http/bin/gen-api-test/index.ts +1 -0
  86. package/src/http/bin/gen-api-type/calc-code.ts +25 -0
  87. package/src/http/bin/gen-api-type/gen-api-type.ts +127 -0
  88. package/src/http/bin/gen-api-type/index.ts +2 -0
  89. package/src/http/bin/index.ts +2 -0
  90. package/src/http/index.ts +3 -0
  91. package/src/huawei/README.md +1 -0
  92. package/src/huawei/index.ts +2 -0
  93. package/src/huawei/moderation/index.ts +1 -0
  94. package/src/huawei/moderation/moderation.ts +355 -0
  95. package/src/huawei/obs/esdk-obs-nodejs.d.ts +87 -0
  96. package/src/huawei/obs/index.ts +1 -0
  97. package/src/huawei/obs/obs.ts +42 -0
  98. package/src/index.ts +19 -2
  99. package/src/json/README.md +92 -0
  100. package/src/json/index.ts +1 -0
  101. package/src/json/repair.ts +18 -0
  102. package/src/log/logger.ts +15 -4
  103. package/src/openai/README.md +1 -0
  104. package/src/openai/index.ts +1 -0
  105. package/src/openai/openai.ts +510 -0
  106. package/src/orchestration/README.md +9 -7
  107. package/src/orchestration/dispatching/dispatcher.ts +83 -0
  108. package/src/orchestration/dispatching/index.ts +2 -0
  109. package/src/orchestration/dispatching/selector/base-selector.ts +39 -0
  110. package/src/orchestration/dispatching/selector/down-count-selector.ts +119 -0
  111. package/src/orchestration/dispatching/selector/index.ts +2 -0
  112. package/src/orchestration/index.ts +2 -0
  113. package/src/orchestration/scheduling/index.ts +2 -0
  114. package/src/orchestration/scheduling/scheduler.ts +103 -0
  115. package/src/orchestration/scheduling/task.ts +32 -0
  116. package/src/random/README.md +8 -7
  117. package/src/random/base.ts +66 -0
  118. package/src/random/index.ts +5 -1
  119. package/src/random/random-boolean.ts +40 -0
  120. package/src/random/random-integer.ts +60 -0
  121. package/src/random/random-number.ts +72 -0
  122. package/src/random/random-string.ts +66 -0
  123. package/src/request/README.md +108 -0
  124. package/src/request/fetch/base.ts +108 -0
  125. package/src/request/fetch/browser.ts +285 -0
  126. package/src/request/fetch/general.ts +20 -0
  127. package/src/request/fetch/index.ts +4 -0
  128. package/src/request/fetch/nodejs.ts +285 -0
  129. package/src/request/index.ts +2 -0
  130. package/src/request/request/base.ts +250 -0
  131. package/src/request/request/general.ts +64 -0
  132. package/src/request/request/index.ts +3 -0
  133. package/src/request/request/resource.ts +68 -0
  134. package/src/result/README.md +4 -0
  135. package/src/result/controller.ts +54 -0
  136. package/src/result/either.ts +193 -0
  137. package/src/result/index.ts +2 -0
  138. package/src/route/README.md +105 -0
  139. package/src/route/adapter/browser.ts +122 -0
  140. package/src/route/adapter/driver.ts +56 -0
  141. package/src/route/adapter/index.ts +2 -0
  142. package/src/route/index.ts +3 -0
  143. package/src/route/router/index.ts +2 -0
  144. package/src/route/router/route.ts +630 -0
  145. package/src/route/router/router.ts +1642 -0
  146. package/src/route/uri/hash.ts +308 -0
  147. package/src/route/uri/index.ts +7 -0
  148. package/src/route/uri/pathname.ts +376 -0
  149. package/src/route/uri/search.ts +413 -0
  150. package/src/socket/README.md +105 -0
  151. package/src/socket/client/index.ts +2 -0
  152. package/src/socket/client/socket-unit.ts +660 -0
  153. package/src/socket/client/socket.ts +203 -0
  154. package/src/socket/common/index.ts +2 -0
  155. package/src/socket/common/socket-unit-common.ts +23 -0
  156. package/src/socket/common/socket-unit-heartbeat.ts +427 -0
  157. package/src/socket/index.ts +3 -0
  158. package/src/socket/server/index.ts +3 -0
  159. package/src/socket/server/server.ts +183 -0
  160. package/src/socket/server/socket-unit.ts +449 -0
  161. package/src/socket/server/socket.ts +264 -0
  162. package/src/storage/table.ts +3 -3
  163. package/src/timer/expiration/expiration-manager.ts +3 -3
  164. package/src/timer/expiration/remaining-manager.ts +3 -3
  165. package/src/tube/README.md +99 -0
  166. package/src/tube/helper.ts +138 -0
  167. package/src/tube/index.ts +2 -0
  168. package/src/tube/tube.ts +880 -0
  169. package/src/weixin/README.md +1 -0
  170. package/src/weixin/index.ts +2 -0
  171. package/src/weixin/official-account/authorization.ts +159 -0
  172. package/src/weixin/official-account/index.ts +2 -0
  173. package/src/weixin/official-account/js-api.ts +134 -0
  174. package/src/weixin/open/index.ts +1 -0
  175. package/src/weixin/open/oauth2.ts +133 -0
  176. package/tests/unit/ai/ai.spec.ts +85 -0
  177. package/tests/unit/aio/content.spec.ts +105 -0
  178. package/tests/unit/aio/json.spec.ts +147 -0
  179. package/tests/unit/aio/prompt.spec.ts +111 -0
  180. package/tests/unit/basic/error.spec.ts +16 -4
  181. package/tests/unit/basic/schedule.spec.ts +74 -0
  182. package/tests/unit/basic/stream.spec.ts +90 -37
  183. package/tests/unit/credential/api-key.spec.ts +37 -0
  184. package/tests/unit/credential/bearer.spec.ts +23 -0
  185. package/tests/unit/credential/json-web-token.spec.ts +23 -0
  186. package/tests/unit/credential/password.spec.ts +41 -0
  187. package/tests/unit/cron/cron.spec.ts +84 -0
  188. package/tests/unit/event/class-event-proxy.spec.ts +3 -3
  189. package/tests/unit/event/event-manager.spec.ts +3 -3
  190. package/tests/unit/event/instance-event-proxy.spec.ts +3 -3
  191. package/tests/unit/form/inputor-controller/base.spec.ts +458 -0
  192. package/tests/unit/form/inputor-controller/boolean.spec.ts +30 -0
  193. package/tests/unit/form/inputor-controller/file.spec.ts +27 -0
  194. package/tests/unit/form/inputor-controller/form.spec.ts +120 -0
  195. package/tests/unit/form/inputor-controller/helper.spec.ts +67 -0
  196. package/tests/unit/form/inputor-controller/multi-select.spec.ts +34 -0
  197. package/tests/unit/form/inputor-controller/number.spec.ts +36 -0
  198. package/tests/unit/form/inputor-controller/select.spec.ts +49 -0
  199. package/tests/unit/form/inputor-controller/text.spec.ts +34 -0
  200. package/tests/unit/http/api/api-core-host.spec.ts +207 -0
  201. package/tests/unit/http/api/api-schema.spec.ts +120 -0
  202. package/tests/unit/http/api/api-server.spec.ts +363 -0
  203. package/tests/unit/http/api/api-test.spec.ts +117 -0
  204. package/tests/unit/http/api/api.spec.ts +121 -0
  205. package/tests/unit/http/api-adapter/node-http.spec.ts +191 -0
  206. package/tests/unit/json/repair.spec.ts +11 -0
  207. package/tests/unit/log/logger.spec.ts +19 -4
  208. package/tests/unit/openai/openai.spec.ts +64 -0
  209. package/tests/unit/orchestration/dispatching/dispatcher.spec.ts +41 -0
  210. package/tests/unit/orchestration/dispatching/selector/down-count-selector.spec.ts +81 -0
  211. package/tests/unit/orchestration/scheduling/scheduler.spec.ts +103 -0
  212. package/tests/unit/random/base.spec.ts +58 -0
  213. package/tests/unit/random/random-boolean.spec.ts +25 -0
  214. package/tests/unit/random/random-integer.spec.ts +32 -0
  215. package/tests/unit/random/random-number.spec.ts +33 -0
  216. package/tests/unit/random/random-string.spec.ts +22 -0
  217. package/tests/unit/request/fetch/browser.spec.ts +222 -0
  218. package/tests/unit/request/fetch/general.spec.ts +43 -0
  219. package/tests/unit/request/fetch/nodejs.spec.ts +225 -0
  220. package/tests/unit/request/request/base.spec.ts +385 -0
  221. package/tests/unit/request/request/general.spec.ts +161 -0
  222. package/tests/unit/route/router/route.spec.ts +431 -0
  223. package/tests/unit/route/router/router.spec.ts +407 -0
  224. package/tests/unit/route/uri/hash.spec.ts +72 -0
  225. package/tests/unit/route/uri/pathname.spec.ts +147 -0
  226. package/tests/unit/route/uri/search.spec.ts +107 -0
  227. package/tests/unit/socket/client.spec.ts +208 -0
  228. package/tests/unit/socket/server.spec.ts +135 -0
  229. package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
  230. package/tests/unit/tube/helper.spec.ts +139 -0
  231. package/tests/unit/tube/tube.spec.ts +501 -0
  232. package/src/random/string.ts +0 -35
  233. package/tests/unit/random/string.spec.ts +0 -11
@@ -0,0 +1,191 @@
1
+ import type { AddressInfo } from "node:net"
2
+
3
+ import { afterEach, expect, test } from "vitest"
4
+
5
+ import { Http } from "#Source/index.ts"
6
+
7
+ afterEach(async () => {
8
+ await Promise.resolve()
9
+ })
10
+
11
+ test("ApiHostNodeHttp exposes usable URLs and forwards request data to ApiCoreNodeHttp", async () => {
12
+ const apiHost = Http.createApiHostNodeHttp({ port: 0 })
13
+
14
+ apiHost.attachApiCoreHandler(async (apiCore) => {
15
+ const path = await apiCore.getRequestPath()
16
+ const method = await apiCore.getRequestMethod()
17
+ const search = await apiCore.getRequestSearch()
18
+ const bodyJson = await apiCore.getRequestBodyJson()
19
+
20
+ await apiCore.json({
21
+ path,
22
+ method,
23
+ search,
24
+ bodyJson,
25
+ })
26
+ })
27
+
28
+ const startResult = await apiHost.start()
29
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
30
+ const address = apiHost.safeGetServer().address() as AddressInfo
31
+
32
+ expect(startResult.urlList).toContain(`http://127.0.0.1:${address.port}`)
33
+ expect(startResult.urlList).toContain(`http://localhost:${address.port}`)
34
+ expect(startResult.urlList).toContain(`http://[::1]:${address.port}`)
35
+
36
+ const response = await fetch(`http://127.0.0.1:${address.port}/users?page=2`, {
37
+ method: "POST",
38
+ headers: {
39
+ "Content-Type": "application/json",
40
+ },
41
+ body: JSON.stringify({ name: "Mobius" }),
42
+ })
43
+
44
+ await expect(response.json()).resolves.toEqual({
45
+ path: "/users",
46
+ method: "POST",
47
+ search: "?page=2",
48
+ bodyJson: { name: "Mobius" },
49
+ })
50
+
51
+ await apiHost.close()
52
+
53
+ expect(apiHost.isRunning()).toBe(false)
54
+ })
55
+
56
+ test("ApiCoreNodeHttp returns a stable error response when the handler throws", async () => {
57
+ const apiHost = Http.createApiHostNodeHttp({ port: 0 })
58
+
59
+ apiHost.attachApiCoreHandler(() => {
60
+ throw new Error("boom")
61
+ })
62
+
63
+ await apiHost.start()
64
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
65
+ const address = apiHost.safeGetServer().address() as AddressInfo
66
+
67
+ const response = await fetch(`http://127.0.0.1:${address.port}/error`, {
68
+ method: "GET",
69
+ })
70
+
71
+ expect(response.status).toBe(500)
72
+ await expect(response.json()).resolves.toEqual({
73
+ status: "error",
74
+ data: {
75
+ reason: "internal_server_error",
76
+ },
77
+ })
78
+
79
+ await apiHost.close()
80
+ })
81
+
82
+ test("ApiCoreNodeHttp treats an empty request body as an empty object", async () => {
83
+ const apiHost = Http.createApiHostNodeHttp({ port: 0 })
84
+
85
+ apiHost.attachApiCoreHandler(async (apiCore) => {
86
+ await apiCore.json({
87
+ bodyJson: await apiCore.getRequestBodyJson(),
88
+ })
89
+ })
90
+
91
+ await apiHost.start()
92
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
93
+ const address = apiHost.safeGetServer().address() as AddressInfo
94
+
95
+ const response = await fetch(`http://127.0.0.1:${address.port}/empty`, {
96
+ method: "GET",
97
+ })
98
+
99
+ await expect(response.json()).resolves.toEqual({
100
+ bodyJson: {},
101
+ })
102
+
103
+ await apiHost.close()
104
+ })
105
+
106
+ test("ApiCoreNodeHttp exposes normalized headers and parses multipart form data", async () => {
107
+ const apiHost = Http.createApiHostNodeHttp({ port: 0 })
108
+
109
+ apiHost.attachApiCoreHandler(async (apiCore) => {
110
+ const headers = await apiCore.getRequestHeaders()
111
+ const formData = await apiCore.getRequestBodyFormData()
112
+ const files = await apiCore.getRequestFiles()
113
+
114
+ await apiCore.json({
115
+ contentType: headers["content-type"],
116
+ customHeader: await apiCore.getRequestHeader("X-Custom-Header"),
117
+ formData: formData.map((entry) => {
118
+ if (entry.value instanceof File) {
119
+ return {
120
+ name: entry.name,
121
+ value: {
122
+ kind: "file",
123
+ name: entry.value.name,
124
+ size: entry.value.size,
125
+ type: entry.value.type,
126
+ },
127
+ }
128
+ }
129
+
130
+ return {
131
+ name: entry.name,
132
+ value: entry.value,
133
+ }
134
+ }),
135
+ files: files.map((file) => {
136
+ return {
137
+ name: file.name,
138
+ fileName: file.value.name,
139
+ size: file.value.size,
140
+ type: file.value.type,
141
+ }
142
+ }),
143
+ })
144
+ })
145
+
146
+ await apiHost.start()
147
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
148
+ const address = apiHost.safeGetServer().address() as AddressInfo
149
+
150
+ const formData = new FormData()
151
+ formData.append("user", "Mobius")
152
+ formData.append("avatar", new File(["hello"], "avatar.txt", { type: "text/plain" }))
153
+
154
+ const response = await fetch(`http://127.0.0.1:${address.port}/upload`, {
155
+ method: "POST",
156
+ headers: {
157
+ "X-Custom-Header": "alpha",
158
+ },
159
+ body: formData,
160
+ })
161
+
162
+ await expect(response.json()).resolves.toEqual({
163
+ contentType: [expect.stringContaining("multipart/form-data")],
164
+ customHeader: ["alpha"],
165
+ formData: [
166
+ {
167
+ name: "user",
168
+ value: "Mobius",
169
+ },
170
+ {
171
+ name: "avatar",
172
+ value: {
173
+ kind: "file",
174
+ name: "avatar.txt",
175
+ size: 5,
176
+ type: "text/plain",
177
+ },
178
+ },
179
+ ],
180
+ files: [
181
+ {
182
+ name: "avatar",
183
+ fileName: "avatar.txt",
184
+ size: 5,
185
+ type: "text/plain",
186
+ },
187
+ ],
188
+ })
189
+
190
+ await apiHost.close()
191
+ })
@@ -0,0 +1,11 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import { repairJson } from "#Source/json/index.ts"
4
+
5
+ test("repairJson repairs common JSON-like text issues and throws for unrecoverable input", () => {
6
+ expect(repairJson("{foo:1,}")).toBe('{"foo":1}')
7
+ expect(repairJson("{'name':'mobius',}")).toBe('{"name":"mobius"}')
8
+ expect(repairJson('{"items":[1,2,],}')).toBe('{"items":[1,2]}')
9
+
10
+ expect(() => repairJson('{"a":1,,"b":2}')).toThrow()
11
+ })
@@ -35,19 +35,34 @@ const makeMemoryEmitter = (sink: string[]): LogEmitterItem => {
35
35
  test("Logger.fromOptions returns provided logger and otherwise derives from global logger", () => {
36
36
  const explicit = new Logger({ name: "Explicit" })
37
37
  const resolvedExplicit = Logger.fromOptions({ logger: explicit })
38
- const resolvedDerived = Logger.fromOptions({})
38
+ const resolvedDerived = Logger.fromOptions({}).setDefaultName("Derived")
39
39
 
40
40
  expect(resolvedExplicit).toBe(explicit)
41
41
  expect(resolvedDerived.hasParent()).toBe(true)
42
- expect(resolvedDerived.getNameTags()).toContain(getGlobalLogger().getName())
42
+ expect(resolvedDerived.getNameTags()).toEqual([getGlobalLogger().getName(), "Derived"])
43
43
  })
44
44
 
45
45
  test("Logger.derive creates child logger with inherited configs", () => {
46
46
  const parent = new Logger({ name: "Parent" })
47
- const child = Logger.derive(parent)
47
+ const child = Logger.derive(parent).setDefaultName("Child")
48
48
 
49
49
  expect(child.hasParent()).toBe(true)
50
- expect(child.getNameTags()).toEqual(["Parent", "Unnamed"])
50
+ expect(child.getNameTags()).toEqual(["Parent", "Child"])
51
+ })
52
+
53
+ test("Logger name APIs prefer explicit name and fall back to defaultName", () => {
54
+ const logger = new Logger({})
55
+
56
+ expect(logger.getName()).toBe("Unnamed")
57
+
58
+ logger.setDefaultName("DefaultLogger")
59
+ expect(logger.getName()).toBe("DefaultLogger")
60
+
61
+ logger.setName("ExplicitLogger")
62
+ expect(logger.getName()).toBe("ExplicitLogger")
63
+
64
+ logger.setName(undefined)
65
+ expect(logger.getName()).toBe("DefaultLogger")
51
66
  })
52
67
 
53
68
  test("Logger config APIs merge and apply parent configs", () => {
@@ -0,0 +1,64 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import { streamConsumeInMacroTask } from "#Source/basic/index.ts"
4
+ import { eitherToTuple } from "#Source/result/index.ts"
5
+ import { tubeToReadableStream } from "#Source/tube/index.ts"
6
+ import { Openai } from "#Source/openai/index.ts"
7
+
8
+ const testApiKey = "sk-DLj8aHvKjvHfZ7IHA2F07c968c3d4aE491C1525e40E24932"
9
+ const testBaseUrl = "https://cn.api.openai-next.com/v1/"
10
+ const openai = new Openai({
11
+ apiKey: testApiKey,
12
+ baseUrl: testBaseUrl,
13
+ })
14
+
15
+ test("customEmbedding ", { timeout: 60_000 }, async () => {
16
+ const embedding = await openai.customEmbedding({
17
+ input: "床前明月光,疑是地上霜。举头望明月,低头思故乡。",
18
+ })
19
+ const [left, right] = eitherToTuple(embedding)
20
+ if (left !== undefined) {
21
+ throw new Error(`请求失败: ${JSON.stringify(left)}`)
22
+ }
23
+ const resultLength = right.embedding.length
24
+ expect(resultLength).toBe(1_536)
25
+ })
26
+
27
+ test("customChatCompletion ", { timeout: 60_000 }, async () => {
28
+ const nonStreamingResult = await openai.customChatCompletionNonStreaming({
29
+ model: "gpt-4o-mini",
30
+ messages: [
31
+ { role: "system", content: "你是讲故事大王。" },
32
+ { role: "user", content: "讲一个小红帽吃掉大灰狼的故事。" },
33
+ ],
34
+ })
35
+ const [left, right] = eitherToTuple(nonStreamingResult)
36
+ if (left !== undefined) {
37
+ throw new Error(`请求失败: ${JSON.stringify(left)}`)
38
+ }
39
+ const contentLength = right.content.length
40
+ expect(contentLength).toBeGreaterThan(0)
41
+ })
42
+
43
+ test("customChatCompletionStreaming ", { timeout: 60_000 }, async () => {
44
+ const [left, right] = eitherToTuple(await openai.customChatCompletionStreaming({
45
+ model: "gpt-4o-mini",
46
+ messages: [
47
+ { role: "system", content: "你是讲故事大王。" },
48
+ { role: "user", content: "讲一个小红帽吃掉大灰狼的故事。" },
49
+ ],
50
+ }))
51
+ if (left !== undefined) {
52
+ throw new Error(`请求失败: ${JSON.stringify(left)}`)
53
+ }
54
+ else {
55
+ const stream = tubeToReadableStream(right.completionTube)
56
+ streamConsumeInMacroTask({
57
+ readableStream: stream,
58
+ onValue: (chunk) => {
59
+ console.log(JSON.stringify(chunk.content.total, null, 2))
60
+ // process.stdout.write()
61
+ },
62
+ })
63
+ }
64
+ })
@@ -0,0 +1,41 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import { Dispatcher, DownCountSelector } from "#Source/orchestration/index.ts"
4
+
5
+ test("Dispatcher getSelector reuses selectors by id and applies filter on first creation", async () => {
6
+ const dispatcher = new Dispatcher({
7
+ itemList: ["alpha", "beta", "gamma"],
8
+ })
9
+
10
+ const firstResult = dispatcher.getSelector({
11
+ id: "shared",
12
+ filter: (item) => item !== "alpha",
13
+ })
14
+ const secondResult = dispatcher.getSelector({
15
+ id: "shared",
16
+ filter: () => true,
17
+ })
18
+
19
+ expect(firstResult.selector).toBeInstanceOf(DownCountSelector)
20
+ expect(secondResult.selector).toBe(firstResult.selector)
21
+
22
+ const firstItem = await firstResult.selector.getItem()
23
+
24
+ expect(firstItem.isRight()).toBe(true)
25
+ expect(firstItem.assertRight().getRight().item).toBe("beta")
26
+ })
27
+
28
+ test("Dispatcher destroySelector discards an existing selector and allows recreation", () => {
29
+ const dispatcher = new Dispatcher({
30
+ itemList: ["alpha", "beta"],
31
+ })
32
+
33
+ const firstSelector = dispatcher.getSelector({ id: "shared" }).selector
34
+
35
+ dispatcher.destroySelector("missing")
36
+ dispatcher.destroySelector("shared")
37
+
38
+ const recreatedSelector = dispatcher.getSelector({ id: "shared" }).selector
39
+
40
+ expect(recreatedSelector).not.toBe(firstSelector)
41
+ })
@@ -0,0 +1,81 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import { DownCountSelector } from "#Source/orchestration/index.ts"
4
+
5
+ test("DownCountSelector getId returns the configured selector id", () => {
6
+ const selector = new DownCountSelector({
7
+ id: "selector-1",
8
+ itemList: ["alpha"],
9
+ })
10
+
11
+ expect(selector.getId()).toBe("selector-1")
12
+ })
13
+
14
+ test("DownCountSelector getItem distinguishes no choice, no available, and reset-driven reuse", async () => {
15
+ const emptySelector = new DownCountSelector<string>({
16
+ id: "empty",
17
+ itemList: [],
18
+ })
19
+
20
+ const emptyResult = await emptySelector.getItem()
21
+
22
+ expect(emptyResult.isLeft()).toBe(true)
23
+ expect(emptyResult.assertLeft().getLeft()).toEqual({
24
+ code: "NO_CHOICE",
25
+ })
26
+
27
+ const noAvailableSelector = new DownCountSelector({
28
+ id: "no-available",
29
+ itemList: ["alpha", "beta"],
30
+ resetAllWhenNoAvailable: false,
31
+ })
32
+
33
+ const firstResult = await noAvailableSelector.getItem()
34
+
35
+ expect(firstResult.isRight()).toBe(true)
36
+
37
+ firstResult.assertRight().getRight().markUnavailable()
38
+
39
+ const secondResult = await noAvailableSelector.getItem()
40
+
41
+ expect(secondResult.isRight()).toBe(true)
42
+ expect(secondResult.assertRight().getRight().item).toBe("beta")
43
+
44
+ secondResult.assertRight().getRight().markUnavailable()
45
+
46
+ const noAvailableResult = await noAvailableSelector.getItem()
47
+
48
+ expect(noAvailableResult.isLeft()).toBe(true)
49
+ expect(noAvailableResult.assertLeft().getLeft()).toEqual({
50
+ code: "NO_AVAILABLE",
51
+ })
52
+
53
+ const resetSelector = new DownCountSelector({
54
+ id: "reset",
55
+ itemList: ["only"],
56
+ })
57
+
58
+ const resetFirstResult = await resetSelector.getItem()
59
+
60
+ expect(resetFirstResult.isRight()).toBe(true)
61
+
62
+ resetFirstResult.assertRight().getRight().markUnavailable()
63
+
64
+ const resetSecondResult = await resetSelector.getItem()
65
+
66
+ expect(resetSecondResult.isRight()).toBe(true)
67
+ expect(resetSecondResult.assertRight().getRight().item).toBe("only")
68
+
69
+ resetSecondResult.assertRight().getRight().markUnavailable()
70
+
71
+ const recoveredResult = await resetSelector.getItem()
72
+
73
+ expect(recoveredResult.isRight()).toBe(true)
74
+
75
+ recoveredResult.assertRight().getRight().markAvailable()
76
+
77
+ const availableAgainResult = await resetSelector.getItem()
78
+
79
+ expect(availableAgainResult.isRight()).toBe(true)
80
+ expect(availableAgainResult.assertRight().getRight().item).toBe("only")
81
+ })
@@ -0,0 +1,103 @@
1
+ import { expect, test, vi } from "vitest"
2
+
3
+ import { Scheduler, Task } from "#Source/orchestration/index.ts"
4
+
5
+ test("Scheduler runs one-shot tasks and records run logs", async () => {
6
+ vi.useFakeTimers()
7
+ vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0, 0))
8
+
9
+ try {
10
+ const run = vi.fn(() => undefined)
11
+ const runAt = new Date(Date.now() + 1_000)
12
+ const scheduler = new Scheduler({
13
+ tasks: [
14
+ new Task({
15
+ name: "alpha",
16
+ cron: runAt,
17
+ run,
18
+ }),
19
+ ],
20
+ })
21
+
22
+ scheduler.run()
23
+
24
+ await vi.advanceTimersByTimeAsync(999)
25
+ expect(run).toHaveBeenCalledTimes(0)
26
+
27
+ await vi.advanceTimersByTimeAsync(1)
28
+
29
+ expect(run).toHaveBeenCalledTimes(1)
30
+ expect(scheduler.getErrLog()).toEqual([])
31
+ expect(scheduler.getRunLog()).toHaveLength(1)
32
+ expect(scheduler.getRunLog()[0]).toEqual({
33
+ name: "alpha",
34
+ time: runAt,
35
+ })
36
+ }
37
+ finally {
38
+ vi.clearAllTimers()
39
+ vi.useRealTimers()
40
+ }
41
+ })
42
+
43
+ test("Scheduler records both run and error logs when a task fails", async () => {
44
+ vi.useFakeTimers()
45
+ vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0, 0))
46
+
47
+ try {
48
+ const error = new Error("boom")
49
+ const run = vi.fn(async () => await Promise.reject(error))
50
+ const runAt = new Date(Date.now() + 1_000)
51
+ const scheduler = new Scheduler({
52
+ tasks: [
53
+ new Task({
54
+ name: "beta",
55
+ cron: runAt,
56
+ run,
57
+ }),
58
+ ],
59
+ })
60
+
61
+ scheduler.run()
62
+ await vi.advanceTimersByTimeAsync(1_000)
63
+
64
+ expect(run).toHaveBeenCalledTimes(1)
65
+ expect(scheduler.getRunLog()).toHaveLength(1)
66
+ expect(scheduler.getErrLog()).toEqual([
67
+ {
68
+ name: "beta",
69
+ time: runAt,
70
+ reason: "Error: boom",
71
+ },
72
+ ])
73
+ }
74
+ finally {
75
+ vi.clearAllTimers()
76
+ vi.useRealTimers()
77
+ }
78
+ })
79
+
80
+ test("Scheduler.run rejects duplicate starts", () => {
81
+ vi.useFakeTimers()
82
+ vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0, 0))
83
+
84
+ try {
85
+ const scheduler = new Scheduler({
86
+ tasks: [
87
+ new Task({
88
+ name: "gamma",
89
+ cron: new Date(Date.now() + 1_000),
90
+ run: () => undefined,
91
+ }),
92
+ ],
93
+ })
94
+
95
+ scheduler.run()
96
+
97
+ expect(() => scheduler.run()).toThrow("Scheduler is already running")
98
+ }
99
+ finally {
100
+ vi.clearAllTimers()
101
+ vi.useRealTimers()
102
+ }
103
+ })
@@ -0,0 +1,58 @@
1
+ import { expect, test, vi } from "vitest"
2
+
3
+ import { getRandomIndex, getRandomUint32, getRandomUnitValue } from "#Source/random/index.ts"
4
+
5
+ test("getRandomUint32 returns an integer within uint32 range across available random sources", () => {
6
+ const cryptoBackedValue = getRandomUint32()
7
+ expect(Number.isInteger(cryptoBackedValue)).toBe(true)
8
+ expect(cryptoBackedValue).toBeGreaterThanOrEqual(0)
9
+ expect(cryptoBackedValue).toBeLessThan(0x1_0000_0000)
10
+
11
+ vi.stubGlobal("crypto", undefined)
12
+
13
+ try {
14
+ const fallbackValue = getRandomUint32()
15
+ expect(Number.isInteger(fallbackValue)).toBe(true)
16
+ expect(fallbackValue).toBeGreaterThanOrEqual(0)
17
+ expect(fallbackValue).toBeLessThan(0x1_0000_0000)
18
+ }
19
+ finally {
20
+ vi.unstubAllGlobals()
21
+ }
22
+ })
23
+
24
+ test("getRandomUnitValue returns a unit-interval float across available random sources", () => {
25
+ const cryptoBackedValue = getRandomUnitValue()
26
+ expect(cryptoBackedValue).toBeGreaterThanOrEqual(0)
27
+ expect(cryptoBackedValue).toBeLessThan(1)
28
+
29
+ vi.stubGlobal("crypto", undefined)
30
+
31
+ try {
32
+ const fallbackValue = getRandomUnitValue()
33
+ expect(fallbackValue).toBeGreaterThanOrEqual(0)
34
+ expect(fallbackValue).toBeLessThan(1)
35
+ }
36
+ finally {
37
+ vi.unstubAllGlobals()
38
+ }
39
+ })
40
+
41
+ test("getRandomIndex returns an integer index within alphabet bounds across available random sources", () => {
42
+ const cryptoBackedValue = getRandomIndex(3)
43
+ expect(Number.isInteger(cryptoBackedValue)).toBe(true)
44
+ expect(cryptoBackedValue).toBeGreaterThanOrEqual(0)
45
+ expect(cryptoBackedValue).toBeLessThan(3)
46
+
47
+ vi.stubGlobal("crypto", undefined)
48
+
49
+ try {
50
+ const fallbackValue = getRandomIndex(3)
51
+ expect(Number.isInteger(fallbackValue)).toBe(true)
52
+ expect(fallbackValue).toBeGreaterThanOrEqual(0)
53
+ expect(fallbackValue).toBeLessThan(3)
54
+ }
55
+ finally {
56
+ vi.unstubAllGlobals()
57
+ }
58
+ })
@@ -0,0 +1,25 @@
1
+ import { expect, test, vi } from "vitest"
2
+
3
+ import { randomBoolean } from "#Source/random/index.ts"
4
+
5
+ test("randomBoolean returns expected output across probabilities and available random sources", () => {
6
+ const defaultValue = randomBoolean()
7
+ expect(typeof defaultValue).toBe("boolean")
8
+
9
+ expect(randomBoolean(0)).toBe(false)
10
+ expect(randomBoolean(1)).toBe(true)
11
+ expect(() => randomBoolean(Number.NaN)).toThrow(TypeError)
12
+ expect(() => randomBoolean(Number.POSITIVE_INFINITY)).toThrow(TypeError)
13
+ expect(() => randomBoolean(-0.1)).toThrow(RangeError)
14
+ expect(() => randomBoolean(1.1)).toThrow(RangeError)
15
+
16
+ vi.stubGlobal("crypto", undefined)
17
+
18
+ try {
19
+ const fallbackValue = randomBoolean(0.5)
20
+ expect(typeof fallbackValue).toBe("boolean")
21
+ }
22
+ finally {
23
+ vi.unstubAllGlobals()
24
+ }
25
+ })
@@ -0,0 +1,32 @@
1
+ import { expect, test, vi } from "vitest"
2
+
3
+ import { randomInteger } from "#Source/random/index.ts"
4
+
5
+ test("randomInteger returns expected output across ranges and available random sources", () => {
6
+ const singleBoundValue = randomInteger(5)
7
+ expect(singleBoundValue).toBeGreaterThanOrEqual(0)
8
+ expect(singleBoundValue).toBeLessThanOrEqual(5)
9
+ expect(Number.isInteger(singleBoundValue)).toBe(true)
10
+
11
+ const reversedRangeValue = randomInteger(8, 3)
12
+ expect(reversedRangeValue).toBeGreaterThanOrEqual(3)
13
+ expect(reversedRangeValue).toBeLessThanOrEqual(8)
14
+ expect(Number.isInteger(reversedRangeValue)).toBe(true)
15
+
16
+ expect(randomInteger(4, 4)).toBe(4)
17
+ expect(() => randomInteger(1.5)).toThrow(TypeError)
18
+ expect(() => randomInteger(Number.NaN)).toThrow(TypeError)
19
+ expect(() => randomInteger(1, Number.POSITIVE_INFINITY)).toThrow(TypeError)
20
+
21
+ vi.stubGlobal("crypto", undefined)
22
+
23
+ try {
24
+ const fallbackValue = randomInteger(-2, 2)
25
+ expect(fallbackValue).toBeGreaterThanOrEqual(-2)
26
+ expect(fallbackValue).toBeLessThanOrEqual(2)
27
+ expect(Number.isInteger(fallbackValue)).toBe(true)
28
+ }
29
+ finally {
30
+ vi.unstubAllGlobals()
31
+ }
32
+ })
@@ -0,0 +1,33 @@
1
+ import { expect, test, vi } from "vitest"
2
+
3
+ import { randomNumber } from "#Source/random/index.ts"
4
+
5
+ test("randomNumber returns expected output across ranges and available random sources", () => {
6
+ const defaultValue = randomNumber()
7
+ expect(defaultValue).toBeGreaterThanOrEqual(0)
8
+ expect(defaultValue).toBeLessThan(1)
9
+
10
+ const singleBoundValue = randomNumber(5)
11
+ expect(singleBoundValue).toBeGreaterThanOrEqual(0)
12
+ expect(singleBoundValue).toBeLessThan(5)
13
+
14
+ const reversedRangeValue = randomNumber(10, 3)
15
+ expect(reversedRangeValue).toBeGreaterThanOrEqual(3)
16
+ expect(reversedRangeValue).toBeLessThan(10)
17
+
18
+ expect(randomNumber(4, 4)).toBe(4)
19
+ expect(() => randomNumber(Number.NaN)).toThrow(TypeError)
20
+ expect(() => randomNumber(Number.POSITIVE_INFINITY)).toThrow(TypeError)
21
+ expect(() => randomNumber(1, Number.NEGATIVE_INFINITY)).toThrow(TypeError)
22
+
23
+ vi.stubGlobal("crypto", undefined)
24
+
25
+ try {
26
+ const fallbackValue = randomNumber(-2, 2)
27
+ expect(fallbackValue).toBeGreaterThanOrEqual(-2)
28
+ expect(fallbackValue).toBeLessThan(2)
29
+ }
30
+ finally {
31
+ vi.unstubAllGlobals()
32
+ }
33
+ })