@planet-matrix/mobius-model 0.6.0 → 0.10.1
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 +50 -0
- package/oxlint.config.ts +1 -2
- package/package.json +29 -17
- package/scripts/build.ts +2 -52
- 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 +20 -15
- package/src/basic/error.ts +19 -5
- package/src/basic/function.ts +2 -2
- package/src/basic/index.ts +1 -0
- package/src/basic/promise.ts +141 -71
- package/src/basic/schedule.ts +111 -0
- package/src/basic/stream.ts +135 -25
- 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/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 +198 -0
- package/src/email/README.md +1 -0
- package/src/email/index.ts +1 -0
- package/src/email/resend.ts +25 -0
- package/src/event/class-event-proxy.ts +5 -6
- package/src/event/common.ts +13 -3
- package/src/event/event-manager.ts +3 -3
- package/src/event/instance-event-proxy.ts +5 -6
- package/src/event/internal.ts +4 -4
- package/src/exception/README.md +28 -19
- package/src/exception/error/error.ts +123 -0
- package/src/exception/error/index.ts +2 -0
- package/src/exception/error/match.ts +38 -0
- package/src/exception/error/must-fix.ts +17 -0
- package/src/exception/index.ts +2 -0
- package/src/file-system/find.ts +53 -0
- package/src/file-system/index.ts +2 -0
- package/src/file-system/path.ts +76 -0
- package/src/file-system/resolve.ts +22 -0
- package/src/form/README.md +25 -0
- package/src/form/index.ts +1 -0
- package/src/form/inputor-controller/base.ts +861 -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 +179 -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 +34 -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 +294 -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/index.ts +21 -2
- package/src/json/README.md +92 -0
- package/src/json/index.ts +1 -0
- package/src/json/repair.ts +18 -0
- package/src/log/logger.ts +15 -4
- package/src/openai/README.md +1 -0
- package/src/openai/index.ts +1 -0
- package/src/openai/openai.ts +509 -0
- package/src/orchestration/README.md +9 -7
- 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 +2 -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 +8 -7
- 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/request/README.md +108 -0
- package/src/request/fetch/base.ts +108 -0
- package/src/request/fetch/browser.ts +280 -0
- package/src/request/fetch/general.ts +20 -0
- package/src/request/fetch/index.ts +4 -0
- package/src/request/fetch/nodejs.ts +280 -0
- package/src/request/index.ts +2 -0
- package/src/request/request/base.ts +246 -0
- package/src/request/request/general.ts +63 -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 +58 -0
- package/src/result/either.ts +363 -0
- package/src/result/generator.ts +168 -0
- package/src/result/index.ts +3 -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 +1641 -0
- package/src/route/uri/hash.ts +307 -0
- package/src/route/uri/index.ts +7 -0
- package/src/route/uri/pathname.ts +376 -0
- package/src/route/uri/search.ts +412 -0
- package/src/service/README.md +1 -0
- package/src/service/index.ts +1 -0
- package/src/service/service.ts +110 -0
- package/src/socket/README.md +105 -0
- package/src/socket/client/index.ts +2 -0
- package/src/socket/client/socket-unit.ts +658 -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 +448 -0
- package/src/socket/server/socket.ts +264 -0
- package/src/storage/table.ts +3 -3
- package/src/timer/expiration/expiration-manager.ts +3 -3
- package/src/timer/expiration/remaining-manager.ts +3 -3
- package/src/tube/README.md +99 -0
- package/src/tube/helper.ts +137 -0
- package/src/tube/index.ts +2 -0
- package/src/tube/tube.ts +880 -0
- package/src/weixin/README.md +1 -0
- package/src/weixin/index.ts +2 -0
- package/src/weixin/official-account/authorization.ts +157 -0
- package/src/weixin/official-account/index.ts +2 -0
- package/src/weixin/official-account/js-api.ts +132 -0
- package/src/weixin/open/index.ts +1 -0
- package/src/weixin/open/oauth2.ts +131 -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 +146 -0
- package/tests/unit/aio/prompt.spec.ts +111 -0
- package/tests/unit/basic/error.spec.ts +16 -4
- package/tests/unit/basic/promise.spec.ts +158 -50
- package/tests/unit/basic/schedule.spec.ts +74 -0
- package/tests/unit/basic/stream.spec.ts +90 -37
- package/tests/unit/credential/api-key.spec.ts +36 -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 +40 -0
- package/tests/unit/cron/cron.spec.ts +84 -0
- package/tests/unit/event/class-event-proxy.spec.ts +3 -3
- package/tests/unit/event/event-manager.spec.ts +3 -3
- package/tests/unit/event/instance-event-proxy.spec.ts +3 -3
- package/tests/unit/exception/error/error.spec.ts +83 -0
- package/tests/unit/exception/error/match.spec.ts +81 -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 +187 -0
- package/tests/unit/identifier/uuid.spec.ts +0 -1
- package/tests/unit/json/repair.spec.ts +11 -0
- package/tests/unit/log/logger.spec.ts +19 -4
- package/tests/unit/openai/openai.spec.ts +64 -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/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 +382 -0
- package/tests/unit/request/request/general.spec.ts +160 -0
- package/tests/unit/result/controller.spec.ts +82 -0
- package/tests/unit/result/either.spec.ts +377 -0
- package/tests/unit/result/generator.spec.ts +273 -0
- package/tests/unit/route/router/route.spec.ts +430 -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 +146 -0
- package/tests/unit/route/uri/search.spec.ts +107 -0
- package/tests/unit/socket/client.spec.ts +208 -0
- package/tests/unit/socket/server.spec.ts +133 -0
- package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
- package/tests/unit/tube/helper.spec.ts +139 -0
- package/tests/unit/tube/tube.spec.ts +501 -0
- package/vite.config.ts +2 -1
- package/dist/index.js +0 -50
- package/dist/index.js.map +0 -209
- package/src/random/string.ts +0 -35
- package/tests/unit/random/string.spec.ts +0 -11
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec"
|
|
4
|
+
import { Logger } from "#Source/log/index.ts"
|
|
5
|
+
import {
|
|
6
|
+
JsonModeResponseParser,
|
|
7
|
+
extractJsonBlock,
|
|
8
|
+
} from "#Source/aio/index.ts"
|
|
9
|
+
|
|
10
|
+
interface TestJsonOutput {
|
|
11
|
+
value: number
|
|
12
|
+
label?: string | undefined
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const createSyncSchema = (
|
|
16
|
+
options: { validateCount?: { current: number } | undefined } = {}
|
|
17
|
+
): StandardSchemaV1<unknown, TestJsonOutput> => {
|
|
18
|
+
const validateCount = options.validateCount
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
"~standard": {
|
|
22
|
+
version: 1 as const,
|
|
23
|
+
vendor: "mobius-aio-json-sync-spec",
|
|
24
|
+
validate: (value: unknown): StandardSchemaV1.Result<TestJsonOutput> => {
|
|
25
|
+
if (validateCount !== undefined) {
|
|
26
|
+
validateCount.current = validateCount.current + 1
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (typeof value !== "object" || value === null) {
|
|
30
|
+
return {
|
|
31
|
+
issues: [{ message: "value must be an object" }],
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const record = value as Record<string, unknown>
|
|
36
|
+
if (typeof record["value"] !== "number") {
|
|
37
|
+
return {
|
|
38
|
+
issues: [{ message: "value must be a number" }],
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const output: TestJsonOutput = {
|
|
43
|
+
value: record["value"],
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (typeof record["label"] === "string") {
|
|
47
|
+
output.label = record["label"]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
value: output,
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const createAsyncSchema = (): StandardSchemaV1<unknown, TestJsonOutput> => {
|
|
59
|
+
return {
|
|
60
|
+
"~standard": {
|
|
61
|
+
version: 1 as const,
|
|
62
|
+
vendor: "mobius-aio-json-async-spec",
|
|
63
|
+
validate: async (_value: unknown): Promise<StandardSchemaV1.Result<TestJsonOutput>> => {
|
|
64
|
+
await Promise.resolve()
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
value: {
|
|
68
|
+
value: 1,
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
test("extractJsonBlock returns the first fenced json block and throws when none exists", () => {
|
|
77
|
+
const response = [
|
|
78
|
+
"before",
|
|
79
|
+
"```json",
|
|
80
|
+
"{\"value\":1}",
|
|
81
|
+
"```",
|
|
82
|
+
"```json",
|
|
83
|
+
"{\"value\":2}",
|
|
84
|
+
"```",
|
|
85
|
+
].join("\n")
|
|
86
|
+
|
|
87
|
+
expect(extractJsonBlock(response)).toBe(["```json", "{\"value\":1}", "```"].join("\n"))
|
|
88
|
+
expect(() => extractJsonBlock("plain text only")).toThrow("Failed to extract json block from response")
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test("JsonModeResponseParser.extractJsonContent strips fences, repairs json-like content and falls back to raw text", () => {
|
|
92
|
+
const parser = new JsonModeResponseParser({
|
|
93
|
+
outputSchema: createSyncSchema(),
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const repairedFromFence = parser.extractJsonContent(["header", "```json", "{value:1,}", "```"].join("\n"))
|
|
97
|
+
const repairedFromRawText = parser.extractJsonContent("{label:'mobius',value:2,}")
|
|
98
|
+
|
|
99
|
+
expect(repairedFromFence).toBe('\n{"value":1}\n')
|
|
100
|
+
expect(repairedFromRawText).toBe('{"label":"mobius","value":2}')
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test("JsonModeResponseParser.parse validates sync output, caches by source text and throws for invalid or async schemas", () => {
|
|
104
|
+
const validateCount = { current: 0 }
|
|
105
|
+
const parser = new JsonModeResponseParser({
|
|
106
|
+
outputSchema: createSyncSchema({ validateCount }),
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const first = parser.parse('{"value":1,"label":"ok"}')
|
|
110
|
+
const second = parser.parse('{"value":1,"label":"ok"}')
|
|
111
|
+
|
|
112
|
+
expect(first).toEqual({ value: 1, label: "ok" })
|
|
113
|
+
expect(second).toBe(first)
|
|
114
|
+
expect(validateCount.current).toBe(1)
|
|
115
|
+
|
|
116
|
+
const invalidParser = new JsonModeResponseParser({
|
|
117
|
+
outputSchema: createSyncSchema(),
|
|
118
|
+
})
|
|
119
|
+
expect(() => invalidParser.parse('{"label":"missing-value"}')).toThrow("Variable validation failed")
|
|
120
|
+
|
|
121
|
+
const asyncParser = new JsonModeResponseParser({
|
|
122
|
+
outputSchema: createAsyncSchema(),
|
|
123
|
+
})
|
|
124
|
+
expect(() => asyncParser.parse('{"value":1}')).toThrow("Validation result is a promise")
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test("JsonModeResponseParser.check returns false and logs the failure context when parsing fails", () => {
|
|
128
|
+
const logger = new Logger({
|
|
129
|
+
name: "AioJsonTest",
|
|
130
|
+
configs: {
|
|
131
|
+
enabled: false,
|
|
132
|
+
},
|
|
133
|
+
})
|
|
134
|
+
const logSpy = vi.spyOn(logger, "log")
|
|
135
|
+
const parser = new JsonModeResponseParser({
|
|
136
|
+
logger,
|
|
137
|
+
outputSchema: createSyncSchema(),
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
expect(parser.check('{"value":3}')).toBe(true)
|
|
141
|
+
expect(parser.check('{"label":"missing-value"}')).toBe(false)
|
|
142
|
+
|
|
143
|
+
expect(logSpy).toHaveBeenCalledTimes(2)
|
|
144
|
+
expect(logSpy.mock.calls[0]?.[0]).toBe("check error:")
|
|
145
|
+
expect(logSpy.mock.calls[1]).toEqual(["error response:", '{"label":"missing-value"}'])
|
|
146
|
+
})
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Prompt,
|
|
5
|
+
emptyLine,
|
|
6
|
+
orderedListItem,
|
|
7
|
+
scopeContent,
|
|
8
|
+
text,
|
|
9
|
+
unorderedListItem,
|
|
10
|
+
} from "#Source/aio/index.ts"
|
|
11
|
+
|
|
12
|
+
test("text creates a plain text prompt block", () => {
|
|
13
|
+
expect(text("hello")).toEqual({
|
|
14
|
+
type: "text",
|
|
15
|
+
content: "hello",
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test("emptyLine creates an empty-line prompt block", () => {
|
|
20
|
+
expect(emptyLine()).toEqual({
|
|
21
|
+
type: "empty-line",
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test("scopeContent wraps content with triple-quote boundaries", () => {
|
|
26
|
+
expect(scopeContent("context")).toEqual({
|
|
27
|
+
type: "scope-content",
|
|
28
|
+
content: '"""\ncontext\n"""',
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test("orderedListItem creates an ordered-list prompt block", () => {
|
|
33
|
+
expect(orderedListItem("first")).toEqual({
|
|
34
|
+
type: "ordered-list-item",
|
|
35
|
+
content: "first",
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test("unorderedListItem creates an unordered-list prompt block", () => {
|
|
40
|
+
expect(unorderedListItem("first")).toEqual({
|
|
41
|
+
type: "unordered-list-item",
|
|
42
|
+
content: "first",
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test("Prompt.addBlock appends a block produced by helpers and returns the same instance", async () => {
|
|
47
|
+
const prompt = new Prompt({})
|
|
48
|
+
|
|
49
|
+
expect(prompt.addBlock(helpers => helpers.text("from-helper"))).toBe(prompt)
|
|
50
|
+
await expect(prompt.getPromptInString()).resolves.toBe("from-helper")
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test("Prompt.addText appends a text block", async () => {
|
|
54
|
+
const prompt = new Prompt({})
|
|
55
|
+
|
|
56
|
+
expect(prompt.addText("alpha")).toBe(prompt)
|
|
57
|
+
await expect(prompt.getPromptInString()).resolves.toBe("alpha")
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test("Prompt.addEmptyLine appends an empty line block", async () => {
|
|
61
|
+
const prompt = new Prompt({})
|
|
62
|
+
|
|
63
|
+
expect(prompt.addEmptyLine()).toBe(prompt)
|
|
64
|
+
await expect(prompt.getPromptInString()).resolves.toBe("\r\n")
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test("Prompt.addScopeContent appends a scoped content block", async () => {
|
|
68
|
+
const prompt = new Prompt({})
|
|
69
|
+
|
|
70
|
+
expect(prompt.addScopeContent("body")).toBe(prompt)
|
|
71
|
+
await expect(prompt.getPromptInString()).resolves.toBe('"""\nbody\n"""')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test("Prompt.addOrderedListItem appends an ordered list block", async () => {
|
|
75
|
+
const prompt = new Prompt({})
|
|
76
|
+
|
|
77
|
+
expect(prompt.addOrderedListItem("step")).toBe(prompt)
|
|
78
|
+
await expect(prompt.getPromptInString()).resolves.toBe("1. step")
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test("Prompt.addUnorderedListItem appends an unordered list block", async () => {
|
|
82
|
+
const prompt = new Prompt({})
|
|
83
|
+
|
|
84
|
+
expect(prompt.addUnorderedListItem("item")).toBe(prompt)
|
|
85
|
+
await expect(prompt.getPromptInString()).resolves.toBe("- item")
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test("Prompt.getPromptInString renders prompt blocks with list numbering and resets between block groups", async () => {
|
|
89
|
+
const prompt = new Prompt({})
|
|
90
|
+
|
|
91
|
+
prompt
|
|
92
|
+
.addText("Intro")
|
|
93
|
+
.addOrderedListItem("First")
|
|
94
|
+
.addOrderedListItem("Second")
|
|
95
|
+
.addText("Break")
|
|
96
|
+
.addOrderedListItem("Reset")
|
|
97
|
+
.addUnorderedListItem("One")
|
|
98
|
+
.addUnorderedListItem("Two")
|
|
99
|
+
.addScopeContent("body")
|
|
100
|
+
|
|
101
|
+
await expect(prompt.getPromptInString()).resolves.toBe([
|
|
102
|
+
"Intro",
|
|
103
|
+
"1. First",
|
|
104
|
+
"2. Second",
|
|
105
|
+
"Break",
|
|
106
|
+
"1. Reset",
|
|
107
|
+
"- One",
|
|
108
|
+
"- Two",
|
|
109
|
+
'"""\nbody\n"""',
|
|
110
|
+
].join("\r\n"))
|
|
111
|
+
})
|
|
@@ -2,7 +2,8 @@ import { expect, test } from "vitest"
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
errorIsNetworkError,
|
|
5
|
-
|
|
5
|
+
errorStringify,
|
|
6
|
+
toError,
|
|
6
7
|
} from "#Source/basic/index.ts"
|
|
7
8
|
|
|
8
9
|
test("errorIsNetworkError returns expected values", () => {
|
|
@@ -26,7 +27,18 @@ test("errorIsNetworkError returns expected values", () => {
|
|
|
26
27
|
expect(errorIsNetworkError(safariWithStack)).toBe(false)
|
|
27
28
|
})
|
|
28
29
|
|
|
29
|
-
test("
|
|
30
|
-
expect(
|
|
31
|
-
expect(
|
|
30
|
+
test("errorStringify returns readable output", () => {
|
|
31
|
+
expect(errorStringify(new Error("boom"))).toBe("Error: boom")
|
|
32
|
+
expect(errorStringify(123)).toBe("123")
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test("toError returns expected values", () => {
|
|
36
|
+
const error = new Error("boom")
|
|
37
|
+
|
|
38
|
+
expect(toError(error)).toBe(error)
|
|
39
|
+
|
|
40
|
+
const wrappedError = toError(123)
|
|
41
|
+
|
|
42
|
+
expect(wrappedError).toBeInstanceOf(Error)
|
|
43
|
+
expect(wrappedError.message).toBe("123")
|
|
32
44
|
})
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
import { expect, test, vi } from "vitest"
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
promiseIsFailResult,
|
|
6
6
|
promiseCatch,
|
|
7
|
-
|
|
7
|
+
promiseCreateFailResult,
|
|
8
|
+
promiseDeferred,
|
|
8
9
|
promiseFilterFailResults,
|
|
9
10
|
promiseFilterSuccessResults,
|
|
10
11
|
promiseFinally,
|
|
@@ -17,16 +18,16 @@ import {
|
|
|
17
18
|
} from "#Source/basic/index.ts"
|
|
18
19
|
|
|
19
20
|
test("promiseThen chains and transforms resolved values", async () => {
|
|
20
|
-
const example1 = await promiseThen((value: number) => value * 2
|
|
21
|
-
const example2 = await promiseThen((value: string) => `${value}
|
|
21
|
+
const example1 = await promiseThen(Promise.resolve(3), (value: number) => value * 2)
|
|
22
|
+
const example2 = await promiseThen(Promise.resolve("ok"), (value: string) => `${value}!`)
|
|
22
23
|
|
|
23
24
|
expect(example1).toBe(6)
|
|
24
25
|
expect(example2).toBe("ok!")
|
|
25
26
|
})
|
|
26
27
|
|
|
27
28
|
test("promiseCatch handles rejected and resolved promises", async () => {
|
|
28
|
-
const example1 = await promiseCatch(
|
|
29
|
-
const example2 = await promiseCatch(() => 0
|
|
29
|
+
const example1 = await promiseCatch(Promise.reject(new Error("x")), () => "fallback")
|
|
30
|
+
const example2 = await promiseCatch(Promise.resolve(3), () => 0)
|
|
30
31
|
|
|
31
32
|
expect(example1).toBe("fallback")
|
|
32
33
|
expect(example2).toBe(3)
|
|
@@ -35,32 +36,49 @@ test("promiseCatch handles rejected and resolved promises", async () => {
|
|
|
35
36
|
test("promiseFinally runs finalizer and preserves resolution", async () => {
|
|
36
37
|
let cleaned = false
|
|
37
38
|
|
|
38
|
-
const example1 = await promiseFinally(() => {
|
|
39
|
+
const example1 = await promiseFinally(Promise.resolve(10), () => {
|
|
39
40
|
cleaned = true
|
|
40
|
-
}
|
|
41
|
+
})
|
|
41
42
|
|
|
42
43
|
expect(example1).toBe(10)
|
|
43
44
|
expect(cleaned).toBe(true)
|
|
44
45
|
})
|
|
45
46
|
|
|
46
|
-
test("
|
|
47
|
+
test("promiseDeferred exposes external resolve and reject for the created promise", async () => {
|
|
48
|
+
const resolvedDeferred = promiseDeferred<number>()
|
|
49
|
+
|
|
50
|
+
resolvedDeferred.resolve(42)
|
|
51
|
+
|
|
52
|
+
await expect(resolvedDeferred.promise).resolves.toBe(42)
|
|
53
|
+
expect(typeof resolvedDeferred.resolve).toBe("function")
|
|
54
|
+
expect(typeof resolvedDeferred.reject).toBe("function")
|
|
55
|
+
|
|
56
|
+
const rejectedDeferred = promiseDeferred<number>()
|
|
57
|
+
const reason = new Error("deferred failure")
|
|
58
|
+
|
|
59
|
+
rejectedDeferred.reject(reason)
|
|
60
|
+
|
|
61
|
+
await expect(rejectedDeferred.promise).rejects.toBe(reason)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test("promiseCreateFailResult creates standardized failure objects", () => {
|
|
47
65
|
const reason = new Error("x")
|
|
48
|
-
const failResult =
|
|
66
|
+
const failResult = promiseCreateFailResult(reason)
|
|
49
67
|
|
|
50
|
-
expect(
|
|
68
|
+
expect(promiseIsFailResult(failResult)).toBe(true)
|
|
51
69
|
expect(failResult.reason).toBe(reason)
|
|
52
70
|
})
|
|
53
71
|
|
|
54
|
-
test("
|
|
55
|
-
const failResult =
|
|
72
|
+
test("promiseIsFailResult identifies standardized failure objects", () => {
|
|
73
|
+
const failResult = promiseCreateFailResult(new Error("x"))
|
|
56
74
|
|
|
57
|
-
expect(
|
|
58
|
-
expect(
|
|
59
|
-
expect(
|
|
75
|
+
expect(promiseIsFailResult(failResult)).toBe(true)
|
|
76
|
+
expect(promiseIsFailResult({ reason: "x" })).toBe(false)
|
|
77
|
+
expect(promiseIsFailResult(null)).toBe(false)
|
|
60
78
|
})
|
|
61
79
|
|
|
62
80
|
test("promiseFilterSuccessResults keeps only successful values", () => {
|
|
63
|
-
const failResult =
|
|
81
|
+
const failResult = promiseCreateFailResult(new Error("x"))
|
|
64
82
|
|
|
65
83
|
const filtered = promiseFilterSuccessResults([1, failResult, 2])
|
|
66
84
|
|
|
@@ -83,93 +101,119 @@ test("promiseFilterFailResults keeps failure values with original indices", asyn
|
|
|
83
101
|
expect(filtered).toHaveLength(2)
|
|
84
102
|
expect(filtered[0]?.index).toBe(1)
|
|
85
103
|
expect(filtered[1]?.index).toBe(2)
|
|
86
|
-
expect(
|
|
87
|
-
expect(
|
|
104
|
+
expect(promiseIsFailResult(filtered[0])).toBe(true)
|
|
105
|
+
expect(promiseIsFailResult(filtered[1])).toBe(true)
|
|
88
106
|
})
|
|
89
107
|
|
|
90
108
|
test("promiseQueue executes makers in sequence and keeps failed results", async () => {
|
|
91
109
|
const results = await promiseQueue<number>([
|
|
92
110
|
async (): Promise<number> => 1,
|
|
93
111
|
async ({ previousResult, index }): Promise<number> => {
|
|
94
|
-
return
|
|
112
|
+
return promiseIsFailResult(previousResult) ? -1 : previousResult + index + 1
|
|
95
113
|
},
|
|
96
114
|
async ({ previousResult }): Promise<number> => {
|
|
97
|
-
if (
|
|
115
|
+
if (promiseIsFailResult(previousResult)) {
|
|
98
116
|
return -1
|
|
99
117
|
}
|
|
100
118
|
throw new Error(String(previousResult))
|
|
101
119
|
},
|
|
102
120
|
async ({ previousResult }): Promise<number> => {
|
|
103
|
-
return
|
|
121
|
+
return promiseIsFailResult(previousResult) ? -1 : previousResult + 1
|
|
104
122
|
},
|
|
105
123
|
])
|
|
106
124
|
|
|
107
125
|
expect(results[0]).toBe(1)
|
|
108
126
|
expect(results[1]).toBe(3)
|
|
109
|
-
expect(
|
|
127
|
+
expect(promiseIsFailResult(results[2])).toBe(true)
|
|
110
128
|
expect(results[3]).toBe(-1)
|
|
111
129
|
})
|
|
112
130
|
|
|
113
131
|
test("promiseRetryWhile retries while predicate is true", async () => {
|
|
114
|
-
let
|
|
132
|
+
let successAttempts = 0
|
|
115
133
|
|
|
116
134
|
const success = await promiseRetryWhile(
|
|
117
135
|
(value) => value < 3,
|
|
118
136
|
async () => {
|
|
119
|
-
|
|
120
|
-
return
|
|
137
|
+
successAttempts = successAttempts + 1
|
|
138
|
+
return successAttempts
|
|
121
139
|
},
|
|
122
|
-
{
|
|
140
|
+
{ maxTryIndex: 5 },
|
|
123
141
|
)
|
|
124
142
|
|
|
125
|
-
|
|
143
|
+
let failedAttempts = 0
|
|
126
144
|
const failed = await promiseRetryWhile(
|
|
127
145
|
() => true,
|
|
128
146
|
async () => {
|
|
129
|
-
|
|
147
|
+
failedAttempts = failedAttempts + 1
|
|
130
148
|
throw new Error("x")
|
|
131
149
|
},
|
|
132
|
-
{
|
|
150
|
+
{ maxTryIndex: 2 },
|
|
133
151
|
)
|
|
134
152
|
|
|
135
|
-
|
|
136
|
-
|
|
153
|
+
let singleAttemptCount = 0
|
|
154
|
+
const singleAttempt = await promiseRetryWhile(
|
|
155
|
+
() => true,
|
|
156
|
+
async () => {
|
|
157
|
+
singleAttemptCount = singleAttemptCount + 1
|
|
158
|
+
return singleAttemptCount
|
|
159
|
+
},
|
|
160
|
+
{ maxTryIndex: 0 },
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
await expect(promiseRetryWhile(async () => false, async () => 1, { maxTryIndex: -1 })).rejects.toThrow(
|
|
164
|
+
"`maxTryIndex` must be greater than or equal to 0.",
|
|
137
165
|
)
|
|
138
166
|
|
|
139
167
|
expect(success).toBe(3)
|
|
140
|
-
expect(
|
|
141
|
-
expect(
|
|
168
|
+
expect(successAttempts).toBe(3)
|
|
169
|
+
expect(promiseIsFailResult(failed)).toBe(true)
|
|
170
|
+
expect(failedAttempts).toBe(3)
|
|
171
|
+
expect(singleAttempt).toBe(1)
|
|
172
|
+
expect(singleAttemptCount).toBe(1)
|
|
142
173
|
})
|
|
143
174
|
|
|
144
175
|
test("promiseRetryUntil retries until predicate becomes true", async () => {
|
|
145
|
-
let
|
|
176
|
+
let successAttempts = 0
|
|
146
177
|
|
|
147
178
|
const success = await promiseRetryUntil(
|
|
148
|
-
(value,
|
|
179
|
+
(value, index) => value >= 2 && index >= 1,
|
|
149
180
|
async () => {
|
|
150
|
-
|
|
151
|
-
return
|
|
181
|
+
successAttempts = successAttempts + 1
|
|
182
|
+
return successAttempts
|
|
152
183
|
},
|
|
153
|
-
{
|
|
184
|
+
{ maxTryIndex: 5 },
|
|
154
185
|
)
|
|
155
186
|
|
|
156
|
-
|
|
187
|
+
let failedAttempts = 0
|
|
157
188
|
const failed = await promiseRetryUntil(
|
|
158
189
|
() => false,
|
|
159
190
|
async () => {
|
|
160
|
-
|
|
191
|
+
failedAttempts = failedAttempts + 1
|
|
161
192
|
throw new Error("x")
|
|
162
193
|
},
|
|
163
|
-
{
|
|
194
|
+
{ maxTryIndex: 2 },
|
|
164
195
|
)
|
|
165
196
|
|
|
166
|
-
|
|
167
|
-
|
|
197
|
+
let singleAttemptCount = 0
|
|
198
|
+
const singleAttempt = await promiseRetryUntil(
|
|
199
|
+
() => false,
|
|
200
|
+
async () => {
|
|
201
|
+
singleAttemptCount = singleAttemptCount + 1
|
|
202
|
+
return singleAttemptCount
|
|
203
|
+
},
|
|
204
|
+
{ maxTryIndex: 0 },
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
await expect(promiseRetryUntil(async () => true, async () => 1, { maxTryIndex: -1 })).rejects.toThrow(
|
|
208
|
+
"`maxTryIndex` must be greater than or equal to 0.",
|
|
168
209
|
)
|
|
169
210
|
|
|
170
211
|
expect(success).toBe(2)
|
|
171
|
-
expect(
|
|
172
|
-
expect(
|
|
212
|
+
expect(successAttempts).toBe(2)
|
|
213
|
+
expect(promiseIsFailResult(failed)).toBe(true)
|
|
214
|
+
expect(failedAttempts).toBe(3)
|
|
215
|
+
expect(singleAttempt).toBe(1)
|
|
216
|
+
expect(singleAttemptCount).toBe(1)
|
|
173
217
|
})
|
|
174
218
|
|
|
175
219
|
test("promiseInterval runs by interval and returns a stopper", async () => {
|
|
@@ -188,7 +232,7 @@ test("promiseInterval runs by interval and returns a stopper", async () => {
|
|
|
188
232
|
stop()
|
|
189
233
|
vi.advanceTimersByTime(50)
|
|
190
234
|
|
|
191
|
-
expect(calls).toEqual([1, 2
|
|
235
|
+
expect(calls).toEqual([0, 1, 2])
|
|
192
236
|
expect(typeof stop).toBe("function")
|
|
193
237
|
}
|
|
194
238
|
finally {
|
|
@@ -196,6 +240,31 @@ test("promiseInterval runs by interval and returns a stopper", async () => {
|
|
|
196
240
|
}
|
|
197
241
|
})
|
|
198
242
|
|
|
243
|
+
test("promiseInterval logs rejected runs instead of leaving them unhandled", async () => {
|
|
244
|
+
vi.useFakeTimers()
|
|
245
|
+
|
|
246
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined)
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
promiseInterval(10, async () => {
|
|
250
|
+
throw new Error("interval failure")
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
vi.advanceTimersByTime(10)
|
|
254
|
+
await Promise.resolve()
|
|
255
|
+
await Promise.resolve()
|
|
256
|
+
|
|
257
|
+
expect(errorSpy).toHaveBeenCalledTimes(1)
|
|
258
|
+
expect(errorSpy.mock.calls[0]?.[0]).toBe("[promiseInterval] unexpected error occurred:")
|
|
259
|
+
expect(errorSpy.mock.calls[0]?.[1]).toBeInstanceOf(Error)
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
vi.clearAllTimers()
|
|
263
|
+
errorSpy.mockRestore()
|
|
264
|
+
vi.useRealTimers()
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
|
|
199
268
|
test("promiseForever loops continuously and reports rejections", async () => {
|
|
200
269
|
vi.useFakeTimers()
|
|
201
270
|
|
|
@@ -210,20 +279,59 @@ test("promiseForever loops continuously and reports rejections", async () => {
|
|
|
210
279
|
return value
|
|
211
280
|
})
|
|
212
281
|
|
|
213
|
-
promiseForever(promiseMaker, { breakTime: 5, onRejected })
|
|
282
|
+
const result = promiseForever(promiseMaker, { breakTime: 5, onRejected })
|
|
214
283
|
await Promise.resolve()
|
|
215
284
|
await Promise.resolve()
|
|
216
285
|
|
|
217
286
|
expect(promiseMaker).toHaveBeenCalledTimes(1)
|
|
218
287
|
expect(onRejected).toHaveBeenCalledTimes(1)
|
|
219
|
-
expect(onRejected.mock.calls[0]?.[0]).toBe(
|
|
220
|
-
expect(
|
|
288
|
+
expect(onRejected.mock.calls[0]?.[0]).toBe(0)
|
|
289
|
+
expect(promiseIsFailResult(onRejected.mock.calls[0]?.[1])).toBe(true)
|
|
290
|
+
expect(result.index).toBe(0)
|
|
291
|
+
expect(result.isStopped).toBe(false)
|
|
221
292
|
|
|
222
293
|
vi.advanceTimersByTime(16)
|
|
223
294
|
await Promise.resolve()
|
|
224
295
|
await Promise.resolve()
|
|
225
296
|
|
|
226
297
|
expect(promiseMaker.mock.calls.length).toBeGreaterThanOrEqual(2)
|
|
298
|
+
|
|
299
|
+
result.stop()
|
|
300
|
+
expect(result.isStopped).toBe(true)
|
|
301
|
+
}
|
|
302
|
+
finally {
|
|
303
|
+
vi.clearAllTimers()
|
|
304
|
+
vi.useRealTimers()
|
|
305
|
+
}
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
test("promiseForever stop prevents scheduling the next run when current run has not settled", async () => {
|
|
309
|
+
vi.useFakeTimers()
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const resolvers: Array<(value: number) => void> = []
|
|
313
|
+
const promiseMaker = vi.fn(async () => {
|
|
314
|
+
return await new Promise<number>((resolve) => {
|
|
315
|
+
resolvers.push(resolve)
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
const result = promiseForever(promiseMaker, { breakTime: 5 })
|
|
320
|
+
|
|
321
|
+
expect(promiseMaker).toHaveBeenCalledTimes(1)
|
|
322
|
+
|
|
323
|
+
result.stop()
|
|
324
|
+
expect(result.isStopped).toBe(true)
|
|
325
|
+
|
|
326
|
+
resolvers[0]?.(1)
|
|
327
|
+
await Promise.resolve()
|
|
328
|
+
await Promise.resolve()
|
|
329
|
+
|
|
330
|
+
vi.advanceTimersByTime(20)
|
|
331
|
+
await Promise.resolve()
|
|
332
|
+
await Promise.resolve()
|
|
333
|
+
|
|
334
|
+
expect(promiseMaker).toHaveBeenCalledTimes(1)
|
|
227
335
|
}
|
|
228
336
|
finally {
|
|
229
337
|
vi.clearAllTimers()
|