@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.
Files changed (379) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +123 -36
  3. package/dist/index.js +715 -4
  4. package/dist/index.js.map +981 -13
  5. package/oxlint.config.ts +6 -0
  6. package/package.json +36 -18
  7. package/src/abort/README.md +92 -0
  8. package/src/abort/abort-manager.ts +278 -0
  9. package/src/abort/abort-signal-listener-manager.ts +81 -0
  10. package/src/abort/index.ts +2 -0
  11. package/src/ai/README.md +1 -0
  12. package/src/ai/ai.ts +107 -0
  13. package/src/ai/chat-completion-ai/aihubmix-chat-completion.ts +78 -0
  14. package/src/ai/chat-completion-ai/chat-completion-ai.ts +270 -0
  15. package/src/ai/chat-completion-ai/chat-completion.ts +189 -0
  16. package/src/ai/chat-completion-ai/index.ts +7 -0
  17. package/src/ai/chat-completion-ai/lingyiwanwu-chat-completion.ts +78 -0
  18. package/src/ai/chat-completion-ai/ohmygpt-chat-completion.ts +78 -0
  19. package/src/ai/chat-completion-ai/openai-next-chat-completion.ts +78 -0
  20. package/src/ai/embedding-ai/embedding-ai.ts +63 -0
  21. package/src/ai/embedding-ai/embedding.ts +50 -0
  22. package/src/ai/embedding-ai/index.ts +4 -0
  23. package/src/ai/embedding-ai/openai-next-embedding.ts +23 -0
  24. package/src/ai/index.ts +4 -0
  25. package/src/aio/README.md +100 -0
  26. package/src/aio/content.ts +141 -0
  27. package/src/aio/index.ts +3 -0
  28. package/src/aio/json.ts +127 -0
  29. package/src/aio/prompt.ts +246 -0
  30. package/src/basic/README.md +72 -116
  31. package/src/basic/error.ts +19 -5
  32. package/src/basic/function.ts +83 -64
  33. package/src/basic/index.ts +1 -0
  34. package/src/basic/is.ts +152 -71
  35. package/src/basic/promise.ts +29 -8
  36. package/src/basic/schedule.ts +111 -0
  37. package/src/basic/stream.ts +135 -25
  38. package/src/basic/string.ts +2 -33
  39. package/src/color/README.md +105 -0
  40. package/src/color/index.ts +3 -0
  41. package/src/color/internal.ts +42 -0
  42. package/src/color/rgb/analyze.ts +236 -0
  43. package/src/color/rgb/construct.ts +130 -0
  44. package/src/color/rgb/convert.ts +227 -0
  45. package/src/color/rgb/derive.ts +303 -0
  46. package/src/color/rgb/index.ts +6 -0
  47. package/src/color/rgb/internal.ts +208 -0
  48. package/src/color/rgb/parse.ts +302 -0
  49. package/src/color/rgb/serialize.ts +144 -0
  50. package/src/color/types.ts +57 -0
  51. package/src/color/xyz/analyze.ts +80 -0
  52. package/src/color/xyz/construct.ts +19 -0
  53. package/src/color/xyz/convert.ts +71 -0
  54. package/src/color/xyz/index.ts +3 -0
  55. package/src/color/xyz/internal.ts +23 -0
  56. package/src/credential/README.md +107 -0
  57. package/src/credential/api-key.ts +158 -0
  58. package/src/credential/bearer.ts +73 -0
  59. package/src/credential/index.ts +4 -0
  60. package/src/credential/json-web-token.ts +96 -0
  61. package/src/credential/password.ts +170 -0
  62. package/src/cron/README.md +86 -0
  63. package/src/cron/cron.ts +87 -0
  64. package/src/cron/index.ts +1 -0
  65. package/src/css/README.md +93 -0
  66. package/src/css/class.ts +559 -0
  67. package/src/css/index.ts +1 -0
  68. package/src/drizzle/README.md +1 -0
  69. package/src/drizzle/drizzle.ts +1 -0
  70. package/src/drizzle/helper.ts +47 -0
  71. package/src/drizzle/index.ts +5 -0
  72. package/src/drizzle/infer.ts +52 -0
  73. package/src/drizzle/kysely.ts +8 -0
  74. package/src/drizzle/pagination.ts +200 -0
  75. package/src/email/README.md +1 -0
  76. package/src/email/index.ts +1 -0
  77. package/src/email/resend.ts +25 -0
  78. package/src/encoding/README.md +66 -79
  79. package/src/encoding/base64.ts +13 -4
  80. package/src/environment/README.md +97 -0
  81. package/src/environment/basic.ts +26 -0
  82. package/src/environment/device.ts +311 -0
  83. package/src/environment/feature.ts +285 -0
  84. package/src/environment/geo.ts +337 -0
  85. package/src/environment/index.ts +7 -0
  86. package/src/environment/runtime.ts +400 -0
  87. package/src/environment/snapshot.ts +60 -0
  88. package/src/environment/variable.ts +239 -0
  89. package/src/event/README.md +90 -0
  90. package/src/event/class-event-proxy.ts +229 -0
  91. package/src/event/common.ts +29 -0
  92. package/src/event/event-manager.ts +203 -0
  93. package/src/event/index.ts +4 -0
  94. package/src/event/instance-event-proxy.ts +187 -0
  95. package/src/event/internal.ts +24 -0
  96. package/src/exception/README.md +96 -0
  97. package/src/exception/browser.ts +219 -0
  98. package/src/exception/index.ts +4 -0
  99. package/src/exception/nodejs.ts +169 -0
  100. package/src/exception/normalize.ts +106 -0
  101. package/src/exception/types.ts +99 -0
  102. package/src/form/README.md +25 -0
  103. package/src/form/index.ts +1 -0
  104. package/src/form/inputor-controller/base.ts +874 -0
  105. package/src/form/inputor-controller/boolean.ts +39 -0
  106. package/src/form/inputor-controller/file.ts +39 -0
  107. package/src/form/inputor-controller/form.ts +181 -0
  108. package/src/form/inputor-controller/helper.ts +117 -0
  109. package/src/form/inputor-controller/index.ts +17 -0
  110. package/src/form/inputor-controller/multi-select.ts +99 -0
  111. package/src/form/inputor-controller/number.ts +116 -0
  112. package/src/form/inputor-controller/select.ts +109 -0
  113. package/src/form/inputor-controller/text.ts +82 -0
  114. package/src/http/READMD.md +1 -0
  115. package/src/http/api/api-core.ts +84 -0
  116. package/src/http/api/api-handler.ts +79 -0
  117. package/src/http/api/api-host.ts +47 -0
  118. package/src/http/api/api-result.ts +56 -0
  119. package/src/http/api/api-schema.ts +154 -0
  120. package/src/http/api/api-server.ts +130 -0
  121. package/src/http/api/api-test.ts +142 -0
  122. package/src/http/api/api-type.ts +37 -0
  123. package/src/http/api/api.ts +81 -0
  124. package/src/http/api/index.ts +11 -0
  125. package/src/http/api-adapter/api-core-node-http.ts +260 -0
  126. package/src/http/api-adapter/api-host-node-http.ts +156 -0
  127. package/src/http/api-adapter/api-result-arktype.ts +297 -0
  128. package/src/http/api-adapter/api-result-zod.ts +286 -0
  129. package/src/http/api-adapter/index.ts +5 -0
  130. package/src/http/bin/gen-api-list/gen-api-list.ts +126 -0
  131. package/src/http/bin/gen-api-list/index.ts +1 -0
  132. package/src/http/bin/gen-api-test/gen-api-test.ts +136 -0
  133. package/src/http/bin/gen-api-test/index.ts +1 -0
  134. package/src/http/bin/gen-api-type/calc-code.ts +25 -0
  135. package/src/http/bin/gen-api-type/gen-api-type.ts +127 -0
  136. package/src/http/bin/gen-api-type/index.ts +2 -0
  137. package/src/http/bin/index.ts +2 -0
  138. package/src/http/index.ts +3 -0
  139. package/src/huawei/README.md +1 -0
  140. package/src/huawei/index.ts +2 -0
  141. package/src/huawei/moderation/index.ts +1 -0
  142. package/src/huawei/moderation/moderation.ts +355 -0
  143. package/src/huawei/obs/esdk-obs-nodejs.d.ts +87 -0
  144. package/src/huawei/obs/index.ts +1 -0
  145. package/src/huawei/obs/obs.ts +42 -0
  146. package/src/identifier/README.md +92 -0
  147. package/src/identifier/id.ts +119 -0
  148. package/src/identifier/index.ts +2 -0
  149. package/src/identifier/uuid.ts +187 -0
  150. package/src/index.ts +33 -1
  151. package/src/json/README.md +92 -0
  152. package/src/json/index.ts +1 -0
  153. package/src/json/repair.ts +18 -0
  154. package/src/log/README.md +79 -0
  155. package/src/log/index.ts +5 -0
  156. package/src/log/log-emitter.ts +72 -0
  157. package/src/log/log-record.ts +10 -0
  158. package/src/log/log-scheduler.ts +74 -0
  159. package/src/log/log-type.ts +8 -0
  160. package/src/log/logger.ts +554 -0
  161. package/src/openai/README.md +1 -0
  162. package/src/openai/index.ts +1 -0
  163. package/src/openai/openai.ts +510 -0
  164. package/src/orchestration/README.md +91 -0
  165. package/src/orchestration/coordination/barrier.ts +214 -0
  166. package/src/orchestration/coordination/count-down-latch.ts +215 -0
  167. package/src/orchestration/coordination/errors.ts +98 -0
  168. package/src/orchestration/coordination/index.ts +16 -0
  169. package/src/orchestration/coordination/internal/wait-constraints.ts +95 -0
  170. package/src/orchestration/coordination/internal/wait-queue.ts +109 -0
  171. package/src/orchestration/coordination/keyed-lock.ts +168 -0
  172. package/src/orchestration/coordination/mutex.ts +257 -0
  173. package/src/orchestration/coordination/permit.ts +127 -0
  174. package/src/orchestration/coordination/read-write-lock.ts +444 -0
  175. package/src/orchestration/coordination/semaphore.ts +280 -0
  176. package/src/orchestration/dispatching/dispatcher.ts +83 -0
  177. package/src/orchestration/dispatching/index.ts +2 -0
  178. package/src/orchestration/dispatching/selector/base-selector.ts +39 -0
  179. package/src/orchestration/dispatching/selector/down-count-selector.ts +119 -0
  180. package/src/orchestration/dispatching/selector/index.ts +2 -0
  181. package/src/orchestration/index.ts +3 -0
  182. package/src/orchestration/scheduling/index.ts +2 -0
  183. package/src/orchestration/scheduling/scheduler.ts +103 -0
  184. package/src/orchestration/scheduling/task.ts +32 -0
  185. package/src/random/README.md +56 -86
  186. package/src/random/base.ts +66 -0
  187. package/src/random/index.ts +5 -1
  188. package/src/random/random-boolean.ts +40 -0
  189. package/src/random/random-integer.ts +60 -0
  190. package/src/random/random-number.ts +72 -0
  191. package/src/random/random-string.ts +66 -0
  192. package/src/reactor/README.md +4 -0
  193. package/src/reactor/reactor-core/primitive.ts +9 -9
  194. package/src/reactor/reactor-core/reactive-system.ts +5 -5
  195. package/src/request/README.md +108 -0
  196. package/src/request/fetch/base.ts +108 -0
  197. package/src/request/fetch/browser.ts +285 -0
  198. package/src/request/fetch/general.ts +20 -0
  199. package/src/request/fetch/index.ts +4 -0
  200. package/src/request/fetch/nodejs.ts +285 -0
  201. package/src/request/index.ts +2 -0
  202. package/src/request/request/base.ts +250 -0
  203. package/src/request/request/general.ts +64 -0
  204. package/src/request/request/index.ts +3 -0
  205. package/src/request/request/resource.ts +68 -0
  206. package/src/result/README.md +4 -0
  207. package/src/result/controller.ts +54 -0
  208. package/src/result/either.ts +193 -0
  209. package/src/result/index.ts +2 -0
  210. package/src/route/README.md +105 -0
  211. package/src/route/adapter/browser.ts +122 -0
  212. package/src/route/adapter/driver.ts +56 -0
  213. package/src/route/adapter/index.ts +2 -0
  214. package/src/route/index.ts +3 -0
  215. package/src/route/router/index.ts +2 -0
  216. package/src/route/router/route.ts +630 -0
  217. package/src/route/router/router.ts +1642 -0
  218. package/src/route/uri/hash.ts +308 -0
  219. package/src/route/uri/index.ts +7 -0
  220. package/src/route/uri/pathname.ts +376 -0
  221. package/src/route/uri/search.ts +413 -0
  222. package/src/singleton/README.md +79 -0
  223. package/src/singleton/factory.ts +55 -0
  224. package/src/singleton/index.ts +2 -0
  225. package/src/singleton/manager.ts +204 -0
  226. package/src/socket/README.md +105 -0
  227. package/src/socket/client/index.ts +2 -0
  228. package/src/socket/client/socket-unit.ts +660 -0
  229. package/src/socket/client/socket.ts +203 -0
  230. package/src/socket/common/index.ts +2 -0
  231. package/src/socket/common/socket-unit-common.ts +23 -0
  232. package/src/socket/common/socket-unit-heartbeat.ts +427 -0
  233. package/src/socket/index.ts +3 -0
  234. package/src/socket/server/index.ts +3 -0
  235. package/src/socket/server/server.ts +183 -0
  236. package/src/socket/server/socket-unit.ts +449 -0
  237. package/src/socket/server/socket.ts +264 -0
  238. package/src/storage/README.md +107 -0
  239. package/src/storage/index.ts +1 -0
  240. package/src/storage/table.ts +449 -0
  241. package/src/timer/README.md +86 -0
  242. package/src/timer/expiration/expiration-manager.ts +594 -0
  243. package/src/timer/expiration/index.ts +3 -0
  244. package/src/timer/expiration/min-heap.ts +208 -0
  245. package/src/timer/expiration/remaining-manager.ts +241 -0
  246. package/src/timer/index.ts +1 -0
  247. package/src/tube/README.md +99 -0
  248. package/src/tube/helper.ts +138 -0
  249. package/src/tube/index.ts +2 -0
  250. package/src/tube/tube.ts +880 -0
  251. package/src/type/README.md +54 -307
  252. package/src/type/class.ts +2 -2
  253. package/src/type/index.ts +14 -14
  254. package/src/type/is.ts +265 -2
  255. package/src/type/object.ts +37 -0
  256. package/src/type/string.ts +7 -2
  257. package/src/type/tuple.ts +6 -6
  258. package/src/type/union.ts +16 -0
  259. package/src/web/README.md +77 -0
  260. package/src/web/capture.ts +35 -0
  261. package/src/web/clipboard.ts +97 -0
  262. package/src/web/dom.ts +117 -0
  263. package/src/web/download.ts +16 -0
  264. package/src/web/event.ts +46 -0
  265. package/src/web/index.ts +10 -0
  266. package/src/web/local-storage.ts +113 -0
  267. package/src/web/location.ts +28 -0
  268. package/src/web/permission.ts +172 -0
  269. package/src/web/script-loader.ts +432 -0
  270. package/src/weixin/README.md +1 -0
  271. package/src/weixin/index.ts +2 -0
  272. package/src/weixin/official-account/authorization.ts +159 -0
  273. package/src/weixin/official-account/index.ts +2 -0
  274. package/src/weixin/official-account/js-api.ts +134 -0
  275. package/src/weixin/open/index.ts +1 -0
  276. package/src/weixin/open/oauth2.ts +133 -0
  277. package/tests/unit/abort/abort-manager.spec.ts +225 -0
  278. package/tests/unit/abort/abort-signal-listener-manager.spec.ts +62 -0
  279. package/tests/unit/ai/ai.spec.ts +85 -0
  280. package/tests/unit/aio/content.spec.ts +105 -0
  281. package/tests/unit/aio/json.spec.ts +147 -0
  282. package/tests/unit/aio/prompt.spec.ts +111 -0
  283. package/tests/unit/basic/array.spec.ts +1 -1
  284. package/tests/unit/basic/error.spec.ts +16 -4
  285. package/tests/unit/basic/schedule.spec.ts +74 -0
  286. package/tests/unit/basic/stream.spec.ts +91 -38
  287. package/tests/unit/basic/string.spec.ts +0 -9
  288. package/tests/unit/color/rgb/analyze.spec.ts +110 -0
  289. package/tests/unit/color/rgb/construct.spec.ts +56 -0
  290. package/tests/unit/color/rgb/convert.spec.ts +60 -0
  291. package/tests/unit/color/rgb/derive.spec.ts +103 -0
  292. package/tests/unit/color/rgb/parse.spec.ts +66 -0
  293. package/tests/unit/color/rgb/serialize.spec.ts +46 -0
  294. package/tests/unit/color/xyz/analyze.spec.ts +33 -0
  295. package/tests/unit/color/xyz/construct.spec.ts +10 -0
  296. package/tests/unit/color/xyz/convert.spec.ts +18 -0
  297. package/tests/unit/credential/api-key.spec.ts +37 -0
  298. package/tests/unit/credential/bearer.spec.ts +23 -0
  299. package/tests/unit/credential/json-web-token.spec.ts +23 -0
  300. package/tests/unit/credential/password.spec.ts +41 -0
  301. package/tests/unit/cron/cron.spec.ts +84 -0
  302. package/tests/unit/css/class.spec.ts +157 -0
  303. package/tests/unit/environment/basic.spec.ts +20 -0
  304. package/tests/unit/environment/device.spec.ts +146 -0
  305. package/tests/unit/environment/feature.spec.ts +388 -0
  306. package/tests/unit/environment/geo.spec.ts +111 -0
  307. package/tests/unit/environment/runtime.spec.ts +364 -0
  308. package/tests/unit/environment/snapshot.spec.ts +4 -0
  309. package/tests/unit/environment/variable.spec.ts +190 -0
  310. package/tests/unit/event/class-event-proxy.spec.ts +225 -0
  311. package/tests/unit/event/event-manager.spec.ts +246 -0
  312. package/tests/unit/event/instance-event-proxy.spec.ts +187 -0
  313. package/tests/unit/exception/browser.spec.ts +213 -0
  314. package/tests/unit/exception/nodejs.spec.ts +144 -0
  315. package/tests/unit/exception/normalize.spec.ts +57 -0
  316. package/tests/unit/form/inputor-controller/base.spec.ts +458 -0
  317. package/tests/unit/form/inputor-controller/boolean.spec.ts +30 -0
  318. package/tests/unit/form/inputor-controller/file.spec.ts +27 -0
  319. package/tests/unit/form/inputor-controller/form.spec.ts +120 -0
  320. package/tests/unit/form/inputor-controller/helper.spec.ts +67 -0
  321. package/tests/unit/form/inputor-controller/multi-select.spec.ts +34 -0
  322. package/tests/unit/form/inputor-controller/number.spec.ts +36 -0
  323. package/tests/unit/form/inputor-controller/select.spec.ts +49 -0
  324. package/tests/unit/form/inputor-controller/text.spec.ts +34 -0
  325. package/tests/unit/http/api/api-core-host.spec.ts +207 -0
  326. package/tests/unit/http/api/api-schema.spec.ts +120 -0
  327. package/tests/unit/http/api/api-server.spec.ts +363 -0
  328. package/tests/unit/http/api/api-test.spec.ts +117 -0
  329. package/tests/unit/http/api/api.spec.ts +121 -0
  330. package/tests/unit/http/api-adapter/node-http.spec.ts +191 -0
  331. package/tests/unit/identifier/id.spec.ts +71 -0
  332. package/tests/unit/identifier/uuid.spec.ts +85 -0
  333. package/tests/unit/json/repair.spec.ts +11 -0
  334. package/tests/unit/log/log-emitter.spec.ts +33 -0
  335. package/tests/unit/log/log-scheduler.spec.ts +40 -0
  336. package/tests/unit/log/log-type.spec.ts +7 -0
  337. package/tests/unit/log/logger.spec.ts +237 -0
  338. package/tests/unit/openai/openai.spec.ts +64 -0
  339. package/tests/unit/orchestration/coordination/barrier.spec.ts +96 -0
  340. package/tests/unit/orchestration/coordination/count-down-latch.spec.ts +63 -0
  341. package/tests/unit/orchestration/coordination/errors.spec.ts +29 -0
  342. package/tests/unit/orchestration/coordination/keyed-lock.spec.ts +109 -0
  343. package/tests/unit/orchestration/coordination/mutex.spec.ts +132 -0
  344. package/tests/unit/orchestration/coordination/permit.spec.ts +43 -0
  345. package/tests/unit/orchestration/coordination/read-write-lock.spec.ts +154 -0
  346. package/tests/unit/orchestration/coordination/semaphore.spec.ts +135 -0
  347. package/tests/unit/orchestration/dispatching/dispatcher.spec.ts +41 -0
  348. package/tests/unit/orchestration/dispatching/selector/down-count-selector.spec.ts +81 -0
  349. package/tests/unit/orchestration/scheduling/scheduler.spec.ts +103 -0
  350. package/tests/unit/random/base.spec.ts +58 -0
  351. package/tests/unit/random/random-boolean.spec.ts +25 -0
  352. package/tests/unit/random/random-integer.spec.ts +32 -0
  353. package/tests/unit/random/random-number.spec.ts +33 -0
  354. package/tests/unit/random/random-string.spec.ts +22 -0
  355. package/tests/unit/reactor/alien-signals-effect.spec.ts +11 -10
  356. package/tests/unit/reactor/preact-signal.spec.ts +1 -2
  357. package/tests/unit/request/fetch/browser.spec.ts +222 -0
  358. package/tests/unit/request/fetch/general.spec.ts +43 -0
  359. package/tests/unit/request/fetch/nodejs.spec.ts +225 -0
  360. package/tests/unit/request/request/base.spec.ts +385 -0
  361. package/tests/unit/request/request/general.spec.ts +161 -0
  362. package/tests/unit/route/router/route.spec.ts +431 -0
  363. package/tests/unit/route/router/router.spec.ts +407 -0
  364. package/tests/unit/route/uri/hash.spec.ts +72 -0
  365. package/tests/unit/route/uri/pathname.spec.ts +147 -0
  366. package/tests/unit/route/uri/search.spec.ts +107 -0
  367. package/tests/unit/singleton/singleton.spec.ts +49 -0
  368. package/tests/unit/socket/client.spec.ts +208 -0
  369. package/tests/unit/socket/server.spec.ts +135 -0
  370. package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
  371. package/tests/unit/storage/table.spec.ts +620 -0
  372. package/tests/unit/timer/expiration/expiration-manager.spec.ts +464 -0
  373. package/tests/unit/timer/expiration/min-heap.spec.ts +71 -0
  374. package/tests/unit/timer/expiration/remaining-manager.spec.ts +234 -0
  375. package/tests/unit/tube/helper.spec.ts +139 -0
  376. package/tests/unit/tube/tube.spec.ts +501 -0
  377. package/.oxlintrc.json +0 -5
  378. package/src/random/uuid.ts +0 -103
  379. package/tests/unit/random/uuid.spec.ts +0 -37
@@ -0,0 +1,78 @@
1
+ import type * as Openai from "#Source/openai/index.ts"
2
+
3
+ import type { BaseChatCompletionOptions, ChatCompletionOptions, ChatCompletionResult } from "./chat-completion.ts"
4
+ import { BaseChatCompletion } from "./chat-completion.ts"
5
+
6
+ export interface OpenaiNextChatCompletionOptions extends BaseChatCompletionOptions {
7
+ openai: Openai.Openai
8
+ }
9
+ export class OpenaiNextChatCompletion extends BaseChatCompletion {
10
+ protected readonly openai: Openai.Openai
11
+
12
+ constructor(options: OpenaiNextChatCompletionOptions) {
13
+ super(options)
14
+ this.logger.setDefaultName("OpenaiNextChatCompletion")
15
+
16
+ this.openai = options.openai
17
+ }
18
+
19
+ protected transformOptions(options: ChatCompletionOptions): Openai.CustomChatCompletionOptions {
20
+ const transformedOptions: Openai.CustomChatCompletionOptions = {
21
+ messages: options.messages.map((message) => {
22
+ const { role, content } = message
23
+ if (role === "system") {
24
+ return message
25
+ }
26
+ if (role === "user") {
27
+ if (typeof content === "string") {
28
+ return {
29
+ role,
30
+ content,
31
+ }
32
+ }
33
+ if (Array.isArray(content)) {
34
+ return {
35
+ role,
36
+ content: content.map((item) => {
37
+ if (item.type === "image_url") {
38
+ return {
39
+ type: item.type,
40
+ image_url: item.imageUrl,
41
+ }
42
+ }
43
+ else {
44
+ return item
45
+ }
46
+ }),
47
+ }
48
+ }
49
+ throw new Error("Invalid content")
50
+ }
51
+ if (role === "assistant") {
52
+ return message
53
+ }
54
+ throw new Error("Invalid role")
55
+ }),
56
+ model: options.model,
57
+ ...(options.frequencyPenalty !== undefined ? { frequency_penalty: options.frequencyPenalty } : {}),
58
+ ...(options.logitBias !== undefined ? { logit_bias: options.logitBias } : {}),
59
+ ...(options.logprobs !== undefined ? { logprobs: options.logprobs } : {}),
60
+ ...(options.maxCompletionTokens !== undefined ? { max_completion_tokens: options.maxCompletionTokens } : {}),
61
+ ...(options.maxTokens !== undefined ? { max_tokens: options.maxTokens } : {}),
62
+ ...(options.presencePenalty !== undefined ? { presence_penalty: options.presencePenalty } : {}),
63
+ ...(options.responseFormat !== undefined ? { response_format: options.responseFormat } : {}),
64
+ ...(options.temperature !== undefined ? { temperature: options.temperature } : {}),
65
+ ...(options.topLogprobs !== undefined ? { top_logprobs: options.topLogprobs } : {}),
66
+ ...(options.topP !== undefined ? { top_p: options.topP } : {}),
67
+ timeout: options.timeoutFirstChunk ?? undefined,
68
+ abortSignal: options.abortSignal,
69
+ }
70
+ return transformedOptions
71
+ }
72
+
73
+ async chatCompletion(options: ChatCompletionOptions): Promise<ChatCompletionResult> {
74
+ const transformedOptions = this.transformOptions(options)
75
+ const result = await this.openai.customChatCompletionStreaming(transformedOptions)
76
+ return result
77
+ }
78
+ }
@@ -0,0 +1,63 @@
1
+ import type { LoggerFriendly, LoggerFriendlyOptions } from "#Source/log/index.ts"
2
+ import { Dispatcher } from "#Source/orchestration/index.ts"
3
+ import { Logger } from "#Source/log/index.ts"
4
+ import { controllerFromEitherType, eitherToTuple } from "#Source/result/index.ts"
5
+
6
+ import type { BaseEmbedding, EmbeddingOptions, EmbeddingResult } from "./embedding.ts"
7
+
8
+ export interface EmbeddingAiOptions extends LoggerFriendlyOptions {
9
+ embeddingInstanceList: BaseEmbedding[]
10
+ /**
11
+ * The maximum number of tries for the request.
12
+ *
13
+ * @default 3
14
+ */
15
+ maxTries?: number | undefined
16
+ }
17
+ interface ResolvedEmbeddingAiOptions {
18
+ embeddingInstanceList: BaseEmbedding[]
19
+ maxTries: number
20
+ }
21
+ export class EmbeddingAi implements LoggerFriendly {
22
+ protected options: ResolvedEmbeddingAiOptions
23
+
24
+ readonly logger: Logger
25
+ private dispatcher: Dispatcher<BaseEmbedding>
26
+
27
+ constructor(options: EmbeddingAiOptions) {
28
+ this.options = {
29
+ embeddingInstanceList: options.embeddingInstanceList,
30
+ maxTries: options.maxTries ?? 3,
31
+ }
32
+
33
+ this.logger = Logger.fromOptions(options).setDefaultName("EmbeddingAi")
34
+ this.dispatcher = new Dispatcher({
35
+ itemList: this.options.embeddingInstanceList,
36
+ logger: Logger.derive(this.logger),
37
+ })
38
+ }
39
+
40
+ async embedding(options: EmbeddingOptions): Promise<EmbeddingResult> {
41
+ const controller = controllerFromEitherType<EmbeddingResult>()
42
+
43
+ const { selector, destroy } = this.dispatcher.getSelector({
44
+ filter: (item) => {
45
+ return item.isSupportModel(options.model) === true
46
+ },
47
+ })
48
+ const [getItemLeft, getItemRight] = eitherToTuple(await selector.getItem())
49
+ if (getItemLeft !== undefined) {
50
+ return await controller.returnLeft(getItemLeft)
51
+ }
52
+ const { item } = getItemRight
53
+
54
+ // TODO: 实现重试逻辑
55
+ // const _maxTries = options.maxTries ?? this.options.maxTries
56
+ const embeddingResult = await item.embedding(options)
57
+
58
+ // 运行至此处,说明 embedding 成功,将 selector 销毁
59
+ destroy()
60
+
61
+ return embeddingResult
62
+ }
63
+ }
@@ -0,0 +1,50 @@
1
+ import type { StringWithAutoCompleteOptions } from "#Source/type/index.ts"
2
+ import type { WithAbortSignal } from "#Source/abort/index.ts"
3
+ import type { LoggerFriendly, LoggerFriendlyOptions } from "#Source/log/index.ts"
4
+ import type { Either } from "#Source/result/index.ts"
5
+
6
+ import { Logger } from "#Source/log/index.ts"
7
+
8
+ const EMBEDDING_MODEL_LIST = [
9
+ "text-embedding-ada-002",
10
+ "text-embedding-3-small",
11
+ "text-embedding-3-large",
12
+ ] as const
13
+ export type EmbeddingModel = typeof EMBEDDING_MODEL_LIST[number]
14
+ export interface EmbeddingOptions extends WithAbortSignal {
15
+ input: string
16
+ model: EmbeddingModel
17
+ maxTries?: number | undefined
18
+ }
19
+
20
+ export interface EmbeddingLeft {
21
+ code: StringWithAutoCompleteOptions<never>
22
+ }
23
+ export interface EmbeddingRight {
24
+ embedding: number[]
25
+ usage: {
26
+ promptTokens: number
27
+ totalTokens: number
28
+ }
29
+ }
30
+ export type EmbeddingResult = Either<EmbeddingLeft, EmbeddingRight>
31
+
32
+ export interface BaseEmbeddingOptions extends LoggerFriendlyOptions {
33
+ }
34
+ export abstract class BaseEmbedding implements LoggerFriendly {
35
+ readonly logger: Logger
36
+
37
+ private readonly supportedModelList: EmbeddingModel[]
38
+
39
+ constructor(options: BaseEmbeddingOptions) {
40
+ this.logger = Logger.fromOptions(options).setDefaultName("BaseEmbedding")
41
+
42
+ this.supportedModelList = [...EMBEDDING_MODEL_LIST]
43
+ }
44
+
45
+ isSupportModel(model: EmbeddingModel): boolean {
46
+ return this.supportedModelList.includes(model)
47
+ }
48
+
49
+ abstract embedding(options: EmbeddingOptions): Promise<EmbeddingResult>
50
+ }
@@ -0,0 +1,4 @@
1
+ export * as EmbeddingAi from "./embedding-ai.ts"
2
+
3
+ export * as Embedding from "./embedding.ts"
4
+ export * as OpenaiNextEmbedding from "./openai-next-embedding.ts"
@@ -0,0 +1,23 @@
1
+ import type * as Openai from "#Source/openai/index.ts"
2
+ import type { BaseEmbeddingOptions, EmbeddingOptions, EmbeddingResult } from "./embedding.ts"
3
+
4
+ import { BaseEmbedding } from "./embedding.ts"
5
+
6
+ export interface OpenaiNextEmbeddingOptions extends BaseEmbeddingOptions {
7
+ openai: Openai.Openai
8
+ }
9
+ export class OpenaiNextEmbedding extends BaseEmbedding {
10
+ protected readonly openai: Openai.Openai
11
+
12
+ constructor(options: OpenaiNextEmbeddingOptions) {
13
+ super(options)
14
+ this.logger.setDefaultName("OpenaiNextEmbedding")
15
+
16
+ this.openai = options.openai
17
+ }
18
+
19
+ async embedding(options: EmbeddingOptions): Promise<EmbeddingResult> {
20
+ const result = await this.openai.customEmbedding(options)
21
+ return result
22
+ }
23
+ }
@@ -0,0 +1,4 @@
1
+ export * as EmbeddingAi from "./embedding-ai/index.ts"
2
+ export * as ChatCompletionAi from "./chat-completion-ai/index.ts"
3
+
4
+ export * from "./ai.ts"
@@ -0,0 +1,100 @@
1
+ # Aio
2
+
3
+ ## Description
4
+
5
+ Aio 模块提供围绕 AI IO(Artificial Intelligence Input / Output,人工智能输入输出)的基础能力,用于组织与 AI 交互时的提示内容、结构化文本输出,以及与这些输入输出直接相关的稳定公共语义。
6
+
7
+ ## For Understanding
8
+
9
+ 理解 Aio 模块时,应先把它视为一个围绕“如何把人类与 AI 之间交换的内容整理为更稳定、更可组合的输入输出结构”而建立的问题域模块,而不是一个泛化的 AI 工具箱,也不是任何与模型调用沾边的能力都能放进来的收纳层。
10
+
11
+ 这里的重点不在于“是否和 AI 有关”,而在于“是否直接服务于 AI 交互内容本身的输入或输出组织”。例如,提示词块(prompt block)组合、结构化响应文本的提取与校验、与 LLM 返回文本直接相连的内容修复入口,都属于这个边界;而模型提供商适配、请求重试策略、会话编排、业务语义解释、工具调用协议实现,则通常属于更上层或其它模块的问题。
12
+
13
+ 当前模块的公共能力可以从三个方向理解:
14
+
15
+ - 内容侧(Content):把流式或分段到达的文本、数值等内容变化表示为可累计、可重建的增量序列。
16
+ - 输入侧(Input):把提示内容组织成可稳定拼接、易于维护和复用的字符串结构。
17
+ - 输出侧(Output):把 AI 返回的文本进一步提取、修复、解析并校验成更可用的结构化结果。
18
+
19
+ 因此,Aio 模块关注的是“交互内容的表达与落地”,而不是“模型本身如何运行”。如果某项能力的核心问题已经转向模型供应商差异、请求生命周期控制、业务工作流状态管理或领域结果解释,那么它通常不应直接落在这里。
20
+
21
+ ## For Using
22
+
23
+ 当你需要把 AI 交互中的输入或输出整理成更稳定的基础模型能力,而不是在业务代码里零散拼接 prompt 字符串、手动提取代码块、或临时校验 LLM 返回 JSON 时,可以使用这个模块。
24
+
25
+ 当前公共能力大致可以按使用意图理解为三类:
26
+
27
+ - 内容增量能力:把一段内容的变化表示为 delta(增量),并在已有内容快照上继续应用这些变化,得到新的总结果。
28
+ - Prompt 组织能力:把文本、空行、作用域内容(scope content)以及简单列表项组合成一段可直接交给 AI 的提示词文本。
29
+ - 结构化响应处理能力:从响应文本中提取 JSON 代码块,必要时进行有限修复,并结合标准 schema 做同步校验,得到可继续使用的结构化结果。
30
+
31
+ 这种能力尤其适合以下场景:
32
+
33
+ - 需要消费流式返回的文本或数值片段,并在过程中维护一份可回放、可重建的累计内容状态。
34
+ - 需要在应用或库中统一构造 prompt,而不希望各处手写拼接规则。
35
+ - 需要消费 LLM 输出的 JSON 文本,但又希望把“提取、修复、解析、校验”这条链路收敛到一个稳定入口。
36
+ - 希望让 AI 输入输出的处理方式可测试、可复用,并尽量减少散落在业务层的文本处理细节。
37
+
38
+ 使用时也应保持边界意识:Aio 模块可以帮助你组织输入输出内容,但它并不负责定义某个业务领域的最终语义。若你的问题已经进入工作流编排、工具调用路由、上下文生命周期管理或业务对象解释,应在更上层继续分层。
39
+
40
+ ## For Contributing
41
+
42
+ 贡献 Aio 模块时,应优先判断新增能力是否直接围绕 AI 交互内容本身的输入或输出展开,而不是因为名称里带有 AI 就把调用链路、业务逻辑或平台适配一起塞进来。这个模块可以继续增长,但增长方向应始终围绕“输入输出内容如何被稳定表达与处理”这一核心边界。
43
+
44
+ 在扩展时,应优先遵守以下边界:
45
+
46
+ - 公共能力应直接服务于内容增量表示、prompt 组织、响应文本提取、结构化输出修复与校验等输入输出问题。
47
+ - 不要把模型供应商适配、请求调度、网络重试、会话状态机、工具调用编排等能力直接放进 Aio 模块。
48
+ - 若某项能力主要解决的是业务语义解释,而不是 AI 输入输出本身,则应放在更合适的上层模块中。
49
+ - 对于具有容错性质的输出处理能力,应保持失败语义清楚;当文本超出可稳定修复或可稳定解析的范围时,抛错通常比返回含糊结果更符合该模块边界。
50
+ - Prompt 相关能力应优先保持组合规则简单、可预期;如果某种格式规则已经复杂到依赖强上下文或业务协议,通常说明它不再只是 Aio 模块内的基础块。
51
+ - Content 相关能力应优先保持增量语义清楚、可回放且可预测;如果某种“内容”抽象已经转向业务级 patch 协议或复杂协同编辑语义,则应考虑放在更专门的模块中。
52
+
53
+ ### JSDoc 注释格式要求
54
+
55
+ - 每个公开导出的目标(类型、函数、变量、类等)都应包含 JSDoc 注释,让人在不跳转实现的情况下就能理解用途。
56
+ - JSDoc 注释第一行应为清晰且简洁的描述,该描述优先使用中文(英文也可以)。
57
+ - 如果描述后还有其他内容,应在描述后加一个空行。
58
+ - 如果有示例,应使用 `@example` 标签,后接三重反引号代码块(不带语言标识)。
59
+ - 如果有示例,应包含多个场景,展示不同用法,尤其要覆盖常见组合方式或边界输入。
60
+ - 如果有示例,应使用注释格式说明每个场景:`// Expect: <result>`。
61
+ - 如果有示例,应将结果赋值给 `example1`、`example2` 之类的变量,以保持示例易读。
62
+ - 如果有示例,`// Expect: <result>` 应该位于 `example1`、`example2` 之前,以保持示例的逻辑清晰。
63
+ - 如果有示例,应优先使用确定性示例;避免断言精确的随机输出。
64
+ - 如果函数返回结构化字符串,应展示其预期格式特征。
65
+ - 如果有参考资料,应将 `@see` 放在 `@example` 代码块之后,并用一个空行分隔。
66
+
67
+ ### 实现规范要求
68
+
69
+ - 不同程序元素之间使用一个空行分隔,保持结构清楚。这里的程序元素,通常指函数、类型、常量,以及直接服务于它们的辅助元素。
70
+ - 某程序元素独占的辅助元素与该程序元素本身视为一个整体,不要在它们之间添加空行。
71
+ - 程序元素的辅助元素应该放置在该程序元素的上方,以保持阅读时的逻辑顺序。
72
+ - 若辅助元素被多个程序元素共享,则应将其视为独立的程序元素,放在这些程序元素中第一个相关目标的上方,并与后续程序元素之间保留一个空行。
73
+ - 辅助元素也应该像其它程序元素一样,保持清晰的命名和适当的注释,以便在需要阅读实现细节时能够快速理解它们的作用和使用方式。
74
+ - 辅助元素的命名必须以前缀 `internal` 开头(或 `Internal`,大小写不敏感)。
75
+ - 辅助元素永远不要公开导出。
76
+ - 被模块内多个不同文件中的程序元素共享的辅助元素,应该放在一个单独的文件中,例如 `./src/aio/internal.ts`;若当前共享逻辑很小,则不必为了形式拆出没有稳定价值的文件。
77
+ - 模块内可以包含子模块。只有当某个子目录表达一个稳定、可单独理解、且可能被父模块重导出的子问题域时,才应将其视为子模块。
78
+ - 子模块包含多个文件时,应该为其单独创建子文件夹,并为其创建单独的 Barrel 文件;父模块的 Barrel 文件再重导出子模块的 Barrel 文件。
79
+ - 子模块不需要有自己的 `README.md`。
80
+ - 子模块可以有自己的 `internal.ts` 文件,多个子模块共享的辅助元素应该放在父模块的 `internal.ts` 文件中,单个子模块共享的辅助元素应该放在该子模块的 `internal.ts` 文件中。
81
+ - 对模块依赖关系的要求(通常是不循环依赖或不反向依赖)与对 DRY 的要求可能产生冲突。此时,若复用的代码数量不大,可以适当牺牲 DRY,复制粘贴并保留必要的注释说明;若复用的代码数量较大,则可以将其抽象到新的文件或子模块中,如 `common.ts`,并在需要的地方导入使用。
82
+ - 实现时应优先保持 AI 输入输出处理规则的显式性与可预期性,避免堆叠过多隐式格式修复规则,导致外部难以理解模块到底承诺了什么。
83
+
84
+ ### 导出策略要求
85
+
86
+ - 保持内部辅助项和内部符号为私有,不要让外部接入依赖临时性的内部结构。
87
+ - 每个模块都应有一个用于重导出所有公共 API 的 Barrel 文件。
88
+ - Barrel 文件应命名为 `index.ts`,放在模块目录根部,并且所有公共 API 都应从该文件导出。
89
+ - 新增公共能力时,应优先检查它是否表达稳定、清楚且值得长期维护的 Aio 模块语义,而不是某段实现细节的便捷暴露;仅在确认需要长期对外承诺时再加入 Barrel 导出。
90
+
91
+ ### 测试要求
92
+
93
+ - 若程序元素是函数,则只为该函数编写一个测试,如果该函数需要测试多个用例,应放在同一个测试中。
94
+ - 若程序元素是类,则至少要为该类的每一个方法编写一个测试,如果该方法需要测试多个用例,应放在同一个测试中。
95
+ - 若程序元素是类,除了为该类的每一个方法编写至少一个测试之外,还可以为该类编写任意多个测试,以覆盖该类的不同使用场景或边界情况。
96
+ - 若编写测试时需要用到辅助元素(Mock 或 Spy 等),可以在测试文件中直接定义这些辅助元素。若辅助元素较为简单,则可以直接放在每一个测试内部,优先保证每个测试的独立性,而不是追求极致 DRY;若辅助元素较为复杂或需要在多个测试中复用,则可以放在测试文件顶部,供该测试文件中的所有测试使用。
97
+ - 测试顺序应与源文件中被测试目标的原始顺序保持一致。
98
+ - 若该模块不需要测试,必须在说明文件中明确说明该模块不需要测试,并说明理由。一般来说,只有在该模块没有可执行的公共函数、只承载类型层表达,或其语义已被上层模块的测试完整覆盖且重复测试几乎不再带来额外价值时,才适合这样处理。
99
+ - 模块的单元测试文件目录是 `./tests/unit/aio`;若未来出现子模块,则子模块的单元测试文件目录为 `./tests/unit/aio/<sub-module-name>`。
100
+ - 测试应覆盖内容增量应用、prompt 组合、结构化响应提取与解析等公共能力的稳定语义,重点验证可预期输入下的结果,以及超出承诺边界时是否以明确失败结束。
@@ -0,0 +1,141 @@
1
+ /**
2
+ * 表示文本内容的追加增量。
3
+ */
4
+ export interface TextContentAppend {
5
+ type: "append"
6
+ text: string
7
+ }
8
+
9
+ /**
10
+ * 表示文本内容的移除增量。
11
+ */
12
+ export interface TextContentRemove {
13
+ type: "remove"
14
+ text: string
15
+ }
16
+
17
+ /**
18
+ * 表示文本内容的重置增量。
19
+ */
20
+ export interface TextContentReset {
21
+ type: "reset"
22
+ }
23
+
24
+ /**
25
+ * 表示文本内容可接受的增量类型。
26
+ */
27
+ export type TextContentDelta = TextContentAppend | TextContentRemove | TextContentReset
28
+
29
+ /**
30
+ * 表示一份文本内容快照及其增量历史。
31
+ */
32
+ export interface TextContent {
33
+ deltaList: TextContentDelta[]
34
+ total: string
35
+ }
36
+
37
+ /**
38
+ * 在现有文本内容快照上应用一个增量,并返回新的内容快照。
39
+ *
40
+ * `append` 会把文本追加到末尾,`remove` 仅在末尾文本匹配时移除,`reset` 会把总文本清空。
41
+ */
42
+ export const applyDeltaToTextContent = (textContent: TextContent, delta: TextContentDelta): TextContent => {
43
+ const clone = structuredClone(textContent)
44
+ const { deltaList, total } = clone
45
+
46
+ const newDeltaList = [...deltaList, delta]
47
+ let newTotal = total
48
+ switch (delta.type) {
49
+ case "append": {
50
+ // append delta text to the end of the total
51
+ newTotal = newTotal + delta.text
52
+ break
53
+ }
54
+ case "remove": {
55
+ // remove delta text from the end of the total, if it exists
56
+ newTotal = newTotal.endsWith(delta.text) ? newTotal.slice(0, newTotal.length - delta.text.length) : newTotal
57
+ break
58
+ }
59
+ case "reset": {
60
+ newTotal = ""
61
+ break
62
+ }
63
+ default: {
64
+ throw new Error(`Unknown delta type: ${String(delta)}`)
65
+ }
66
+ }
67
+ return {
68
+ deltaList: newDeltaList,
69
+ total: newTotal,
70
+ }
71
+ }
72
+
73
+ /**
74
+ * 表示数值内容的追加增量。
75
+ */
76
+ export interface NumberContentAppend {
77
+ type: "append"
78
+ value: number
79
+ }
80
+
81
+ /**
82
+ * 表示数值内容的移除增量。
83
+ */
84
+ export interface NumberContentRemove {
85
+ type: "remove"
86
+ value: number
87
+ }
88
+
89
+ /**
90
+ * 表示数值内容的重置增量。
91
+ */
92
+ export interface NumberContentReset {
93
+ type: "reset"
94
+ }
95
+
96
+ /**
97
+ * 表示数值内容可接受的增量类型。
98
+ */
99
+ export type NumberContentDelta = NumberContentAppend | NumberContentRemove | NumberContentReset
100
+
101
+ /**
102
+ * 表示一份数值内容快照及其增量历史。
103
+ */
104
+ export interface NumberContent {
105
+ deltaList: NumberContentDelta[]
106
+ total: number
107
+ }
108
+
109
+ /**
110
+ * 在现有数值内容快照上应用一个增量,并返回新的内容快照。
111
+ *
112
+ * `append` 会累加数值,`remove` 会扣减数值,`reset` 会把总值重置为 0。
113
+ */
114
+ export const applyDeltaToNumberContent = (numberContent: NumberContent, delta: NumberContentDelta): NumberContent => {
115
+ const clone = structuredClone(numberContent)
116
+ const { deltaList, total } = clone
117
+
118
+ const newDeltaList = [...deltaList, delta]
119
+ let newTotal = total
120
+ switch (delta.type) {
121
+ case "append": {
122
+ newTotal = newTotal + delta.value
123
+ break
124
+ }
125
+ case "remove": {
126
+ newTotal = newTotal - delta.value
127
+ break
128
+ }
129
+ case "reset": {
130
+ newTotal = 0
131
+ break
132
+ }
133
+ default: {
134
+ throw new Error(`Unknown delta type: ${String(delta)}`)
135
+ }
136
+ }
137
+ return {
138
+ deltaList: newDeltaList,
139
+ total: newTotal,
140
+ }
141
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./json.ts"
2
+ export * from "./prompt.ts"
3
+ export * from "./content.ts"
@@ -0,0 +1,127 @@
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec"
2
+ import type { LoggerFriendly, LoggerFriendlyOptions } from "#Source/log/index.ts"
3
+
4
+ import { repairJson } from "#Source/json/index.ts"
5
+ import { Logger } from "#Source/log/index.ts"
6
+ import { isPromise } from "#Source/basic/index.ts"
7
+
8
+ /**
9
+ * 从响应文本中提取第一个 JSON Markdown 代码块。
10
+ *
11
+ * 当前返回值保留原始代码块外壳,即结果仍包含 ```json 与结尾的 ```。
12
+ * 如果文本中不存在符合约定的 JSON 代码块,则会抛出异常。
13
+ */
14
+ export const extractJsonBlock = (response: string): string => {
15
+ try {
16
+ const jsonBlockRegex = /```json\s*([\s\S]*?)\n```/gi
17
+ const match = response.match(jsonBlockRegex)
18
+ if (match === null || match.length === 0) {
19
+ throw new Error("No json block found in response.")
20
+ }
21
+ return match[0]
22
+ }
23
+ catch (exception) {
24
+ throw new Error(`Failed to extract json block from response`, { cause: exception })
25
+ }
26
+ }
27
+
28
+ /**
29
+ * 定义 JSON 模式响应解析器的构造参数。
30
+ */
31
+ export interface JsonModeResponseParserOptions<Output extends StandardSchemaV1> extends LoggerFriendlyOptions {
32
+ outputSchema: Output
33
+ }
34
+
35
+ /**
36
+ * 将 AI 返回文本解析为通过标准 schema 校验的结构化 JSON 结果。
37
+ *
38
+ * 该解析器会按既定顺序执行 JSON 代码块提取、文本修复、JSON.parse 与同步 schema 校验。
39
+ */
40
+ export class JsonModeResponseParser<Output extends StandardSchemaV1> implements LoggerFriendly {
41
+ protected readonly options: JsonModeResponseParserOptions<Output>
42
+
43
+ readonly logger: Logger
44
+ private readonly parsedMap: Map<string, StandardSchemaV1.InferOutput<Output>>
45
+
46
+ constructor(options: JsonModeResponseParserOptions<Output>) {
47
+ this.options = options
48
+
49
+ this.logger = Logger.fromOptions(options).setDefaultName("JsonModeResponseParser")
50
+ this.parsedMap = new Map()
51
+ }
52
+
53
+ /**
54
+ * 从原始文本中抽取并整理可供 JSON.parse 使用的 JSON 内容字符串。
55
+ *
56
+ * 若文本中存在 ```json 代码块,则优先提取该代码块;否则直接把整段文本视为待解析内容。
57
+ * 在提取后会尝试去除代码块包裹并调用 JSON 修复能力,但不会在修复失败时抛出额外错误。
58
+ */
59
+ extractJsonContent(text: string): string {
60
+ // 尝试从 response 中提取 json block
61
+ let assumedJsonBlock = text
62
+ try {
63
+ assumedJsonBlock = extractJsonBlock(text)
64
+ }
65
+ catch {
66
+ // do nothing
67
+ }
68
+
69
+ // 将开头的 ```json 和结尾的 ``` 去除。
70
+ const assumedJsonContent = assumedJsonBlock.trim().replace(/^```json/, "").replace(/```$/, "")
71
+
72
+ // 尝试将 json 修复
73
+ let assumedValidJsonContent = assumedJsonContent
74
+ try {
75
+ assumedValidJsonContent = repairJson(assumedJsonContent)
76
+ }
77
+ catch {
78
+ // do nothing
79
+ }
80
+
81
+ return assumedValidJsonContent
82
+ }
83
+
84
+ /**
85
+ * 解析并校验响应文本,返回满足输出 schema 的结构化结果。
86
+ *
87
+ * 同一段原始文本会命中内部缓存,避免重复解析与重复校验。
88
+ */
89
+ parse(text: string): StandardSchemaV1.InferOutput<Output> {
90
+ if (this.parsedMap.has(text)) {
91
+ return this.parsedMap.get(text)!
92
+ }
93
+
94
+ const assumedValidJsonContent = this.extractJsonContent(text)
95
+
96
+ // 尝试解析 json
97
+ const parsedJson = JSON.parse(assumedValidJsonContent) as unknown
98
+ const validateResult = this.options.outputSchema["~standard"].validate(parsedJson)
99
+ if (isPromise(validateResult)) {
100
+ throw new Error("Validation result is a promise, expected synchronous validation.")
101
+ }
102
+ if (validateResult.issues !== undefined) {
103
+ throw new Error(`Variable validation failed: ${JSON.stringify(validateResult.issues)}`)
104
+ }
105
+
106
+ const value = validateResult.value
107
+ this.parsedMap.set(text, value)
108
+ return value
109
+ }
110
+
111
+ /**
112
+ * 检查响应文本是否能被成功解析并通过 schema 校验。
113
+ *
114
+ * 解析失败时会记录日志并返回 false,而不是向外抛出异常。
115
+ */
116
+ check(text: string): boolean {
117
+ try {
118
+ this.parse(text)
119
+ return true
120
+ }
121
+ catch (exception) {
122
+ this.logger.log("check error:", exception)
123
+ this.logger.log("error response:", text)
124
+ return false
125
+ }
126
+ }
127
+ }