@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.
- package/CHANGELOG.md +37 -0
- package/dist/index.js +706 -36
- package/dist/index.js.map +855 -59
- package/package.json +28 -16
- 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/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 +200 -0
- package/src/email/README.md +1 -0
- package/src/email/index.ts +1 -0
- package/src/email/resend.ts +25 -0
- package/src/event/class-event-proxy.ts +6 -5
- package/src/event/common.ts +13 -3
- package/src/event/event-manager.ts +3 -3
- package/src/event/instance-event-proxy.ts +6 -5
- package/src/event/internal.ts +4 -4
- package/src/form/README.md +25 -0
- package/src/form/index.ts +1 -0
- package/src/form/inputor-controller/base.ts +874 -0
- package/src/form/inputor-controller/boolean.ts +39 -0
- package/src/form/inputor-controller/file.ts +39 -0
- package/src/form/inputor-controller/form.ts +181 -0
- package/src/form/inputor-controller/helper.ts +117 -0
- package/src/form/inputor-controller/index.ts +17 -0
- package/src/form/inputor-controller/multi-select.ts +99 -0
- package/src/form/inputor-controller/number.ts +116 -0
- package/src/form/inputor-controller/select.ts +109 -0
- package/src/form/inputor-controller/text.ts +82 -0
- package/src/http/READMD.md +1 -0
- package/src/http/api/api-core.ts +84 -0
- package/src/http/api/api-handler.ts +79 -0
- package/src/http/api/api-host.ts +47 -0
- package/src/http/api/api-result.ts +56 -0
- package/src/http/api/api-schema.ts +154 -0
- package/src/http/api/api-server.ts +130 -0
- package/src/http/api/api-test.ts +142 -0
- package/src/http/api/api-type.ts +37 -0
- package/src/http/api/api.ts +81 -0
- package/src/http/api/index.ts +11 -0
- package/src/http/api-adapter/api-core-node-http.ts +260 -0
- package/src/http/api-adapter/api-host-node-http.ts +156 -0
- package/src/http/api-adapter/api-result-arktype.ts +297 -0
- package/src/http/api-adapter/api-result-zod.ts +286 -0
- package/src/http/api-adapter/index.ts +5 -0
- package/src/http/bin/gen-api-list/gen-api-list.ts +126 -0
- package/src/http/bin/gen-api-list/index.ts +1 -0
- package/src/http/bin/gen-api-test/gen-api-test.ts +136 -0
- package/src/http/bin/gen-api-test/index.ts +1 -0
- package/src/http/bin/gen-api-type/calc-code.ts +25 -0
- package/src/http/bin/gen-api-type/gen-api-type.ts +127 -0
- package/src/http/bin/gen-api-type/index.ts +2 -0
- package/src/http/bin/index.ts +2 -0
- package/src/http/index.ts +3 -0
- package/src/huawei/README.md +1 -0
- package/src/huawei/index.ts +2 -0
- package/src/huawei/moderation/index.ts +1 -0
- package/src/huawei/moderation/moderation.ts +355 -0
- package/src/huawei/obs/esdk-obs-nodejs.d.ts +87 -0
- package/src/huawei/obs/index.ts +1 -0
- package/src/huawei/obs/obs.ts +42 -0
- package/src/index.ts +19 -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 +510 -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 +285 -0
- package/src/request/fetch/general.ts +20 -0
- package/src/request/fetch/index.ts +4 -0
- package/src/request/fetch/nodejs.ts +285 -0
- package/src/request/index.ts +2 -0
- package/src/request/request/base.ts +250 -0
- package/src/request/request/general.ts +64 -0
- package/src/request/request/index.ts +3 -0
- package/src/request/request/resource.ts +68 -0
- package/src/result/README.md +4 -0
- package/src/result/controller.ts +54 -0
- package/src/result/either.ts +193 -0
- package/src/result/index.ts +2 -0
- package/src/route/README.md +105 -0
- package/src/route/adapter/browser.ts +122 -0
- package/src/route/adapter/driver.ts +56 -0
- package/src/route/adapter/index.ts +2 -0
- package/src/route/index.ts +3 -0
- package/src/route/router/index.ts +2 -0
- package/src/route/router/route.ts +630 -0
- package/src/route/router/router.ts +1642 -0
- package/src/route/uri/hash.ts +308 -0
- package/src/route/uri/index.ts +7 -0
- package/src/route/uri/pathname.ts +376 -0
- package/src/route/uri/search.ts +413 -0
- package/src/socket/README.md +105 -0
- package/src/socket/client/index.ts +2 -0
- package/src/socket/client/socket-unit.ts +660 -0
- package/src/socket/client/socket.ts +203 -0
- package/src/socket/common/index.ts +2 -0
- package/src/socket/common/socket-unit-common.ts +23 -0
- package/src/socket/common/socket-unit-heartbeat.ts +427 -0
- package/src/socket/index.ts +3 -0
- package/src/socket/server/index.ts +3 -0
- package/src/socket/server/server.ts +183 -0
- package/src/socket/server/socket-unit.ts +449 -0
- package/src/socket/server/socket.ts +264 -0
- package/src/storage/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 +138 -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 +159 -0
- package/src/weixin/official-account/index.ts +2 -0
- package/src/weixin/official-account/js-api.ts +134 -0
- package/src/weixin/open/index.ts +1 -0
- package/src/weixin/open/oauth2.ts +133 -0
- package/tests/unit/ai/ai.spec.ts +85 -0
- package/tests/unit/aio/content.spec.ts +105 -0
- package/tests/unit/aio/json.spec.ts +147 -0
- package/tests/unit/aio/prompt.spec.ts +111 -0
- package/tests/unit/basic/error.spec.ts +16 -4
- 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 +37 -0
- package/tests/unit/credential/bearer.spec.ts +23 -0
- package/tests/unit/credential/json-web-token.spec.ts +23 -0
- package/tests/unit/credential/password.spec.ts +41 -0
- package/tests/unit/cron/cron.spec.ts +84 -0
- package/tests/unit/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/form/inputor-controller/base.spec.ts +458 -0
- package/tests/unit/form/inputor-controller/boolean.spec.ts +30 -0
- package/tests/unit/form/inputor-controller/file.spec.ts +27 -0
- package/tests/unit/form/inputor-controller/form.spec.ts +120 -0
- package/tests/unit/form/inputor-controller/helper.spec.ts +67 -0
- package/tests/unit/form/inputor-controller/multi-select.spec.ts +34 -0
- package/tests/unit/form/inputor-controller/number.spec.ts +36 -0
- package/tests/unit/form/inputor-controller/select.spec.ts +49 -0
- package/tests/unit/form/inputor-controller/text.spec.ts +34 -0
- package/tests/unit/http/api/api-core-host.spec.ts +207 -0
- package/tests/unit/http/api/api-schema.spec.ts +120 -0
- package/tests/unit/http/api/api-server.spec.ts +363 -0
- package/tests/unit/http/api/api-test.spec.ts +117 -0
- package/tests/unit/http/api/api.spec.ts +121 -0
- package/tests/unit/http/api-adapter/node-http.spec.ts +191 -0
- package/tests/unit/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 +385 -0
- package/tests/unit/request/request/general.spec.ts +161 -0
- package/tests/unit/route/router/route.spec.ts +431 -0
- package/tests/unit/route/router/router.spec.ts +407 -0
- package/tests/unit/route/uri/hash.spec.ts +72 -0
- package/tests/unit/route/uri/pathname.spec.ts +147 -0
- package/tests/unit/route/uri/search.spec.ts +107 -0
- package/tests/unit/socket/client.spec.ts +208 -0
- package/tests/unit/socket/server.spec.ts +135 -0
- package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
- package/tests/unit/tube/helper.spec.ts +139 -0
- package/tests/unit/tube/tube.spec.ts +501 -0
- package/src/random/string.ts +0 -35
- package/tests/unit/random/string.spec.ts +0 -11
|
@@ -0,0 +1,147 @@
|
|
|
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
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
36
|
+
const record = value as Record<string, unknown>
|
|
37
|
+
if (typeof record["value"] !== "number") {
|
|
38
|
+
return {
|
|
39
|
+
issues: [{ message: "value must be a number" }],
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const output: TestJsonOutput = {
|
|
44
|
+
value: record["value"],
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (typeof record["label"] === "string") {
|
|
48
|
+
output.label = record["label"]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
value: output,
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const createAsyncSchema = (): StandardSchemaV1<unknown, TestJsonOutput> => {
|
|
60
|
+
return {
|
|
61
|
+
"~standard": {
|
|
62
|
+
version: 1 as const,
|
|
63
|
+
vendor: "mobius-aio-json-async-spec",
|
|
64
|
+
validate: async (_value: unknown): Promise<StandardSchemaV1.Result<TestJsonOutput>> => {
|
|
65
|
+
await Promise.resolve()
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
value: {
|
|
69
|
+
value: 1,
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
test("extractJsonBlock returns the first fenced json block and throws when none exists", () => {
|
|
78
|
+
const response = [
|
|
79
|
+
"before",
|
|
80
|
+
"```json",
|
|
81
|
+
"{\"value\":1}",
|
|
82
|
+
"```",
|
|
83
|
+
"```json",
|
|
84
|
+
"{\"value\":2}",
|
|
85
|
+
"```",
|
|
86
|
+
].join("\n")
|
|
87
|
+
|
|
88
|
+
expect(extractJsonBlock(response)).toBe(["```json", "{\"value\":1}", "```"].join("\n"))
|
|
89
|
+
expect(() => extractJsonBlock("plain text only")).toThrow("Failed to extract json block from response")
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test("JsonModeResponseParser.extractJsonContent strips fences, repairs json-like content and falls back to raw text", () => {
|
|
93
|
+
const parser = new JsonModeResponseParser({
|
|
94
|
+
outputSchema: createSyncSchema(),
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
const repairedFromFence = parser.extractJsonContent(["header", "```json", "{value:1,}", "```"].join("\n"))
|
|
98
|
+
const repairedFromRawText = parser.extractJsonContent("{label:'mobius',value:2,}")
|
|
99
|
+
|
|
100
|
+
expect(repairedFromFence).toBe('\n{"value":1}\n')
|
|
101
|
+
expect(repairedFromRawText).toBe('{"label":"mobius","value":2}')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test("JsonModeResponseParser.parse validates sync output, caches by source text and throws for invalid or async schemas", () => {
|
|
105
|
+
const validateCount = { current: 0 }
|
|
106
|
+
const parser = new JsonModeResponseParser({
|
|
107
|
+
outputSchema: createSyncSchema({ validateCount }),
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const first = parser.parse('{"value":1,"label":"ok"}')
|
|
111
|
+
const second = parser.parse('{"value":1,"label":"ok"}')
|
|
112
|
+
|
|
113
|
+
expect(first).toEqual({ value: 1, label: "ok" })
|
|
114
|
+
expect(second).toBe(first)
|
|
115
|
+
expect(validateCount.current).toBe(1)
|
|
116
|
+
|
|
117
|
+
const invalidParser = new JsonModeResponseParser({
|
|
118
|
+
outputSchema: createSyncSchema(),
|
|
119
|
+
})
|
|
120
|
+
expect(() => invalidParser.parse('{"label":"missing-value"}')).toThrow("Variable validation failed")
|
|
121
|
+
|
|
122
|
+
const asyncParser = new JsonModeResponseParser({
|
|
123
|
+
outputSchema: createAsyncSchema(),
|
|
124
|
+
})
|
|
125
|
+
expect(() => asyncParser.parse('{"value":1}')).toThrow("Validation result is a promise")
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test("JsonModeResponseParser.check returns false and logs the failure context when parsing fails", () => {
|
|
129
|
+
const logger = new Logger({
|
|
130
|
+
name: "AioJsonTest",
|
|
131
|
+
configs: {
|
|
132
|
+
enabled: false,
|
|
133
|
+
},
|
|
134
|
+
})
|
|
135
|
+
const logSpy = vi.spyOn(logger, "log")
|
|
136
|
+
const parser = new JsonModeResponseParser({
|
|
137
|
+
logger,
|
|
138
|
+
outputSchema: createSyncSchema(),
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
expect(parser.check('{"value":3}')).toBe(true)
|
|
142
|
+
expect(parser.check('{"label":"missing-value"}')).toBe(false)
|
|
143
|
+
|
|
144
|
+
expect(logSpy).toHaveBeenCalledTimes(2)
|
|
145
|
+
expect(logSpy.mock.calls[0]?.[0]).toBe("check error:")
|
|
146
|
+
expect(logSpy.mock.calls[1]).toEqual(["error response:", '{"label":"missing-value"}'])
|
|
147
|
+
})
|
|
@@ -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
|
})
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
scheduleMacroTask,
|
|
5
|
+
scheduleMacroTaskSimple,
|
|
6
|
+
scheduleMicroTask,
|
|
7
|
+
scheduleMicroTaskSimple,
|
|
8
|
+
} from "#Source/basic/index.ts"
|
|
9
|
+
|
|
10
|
+
test("scheduleMicroTaskSimple schedules a task in the microtask queue", async () => {
|
|
11
|
+
const values: string[] = []
|
|
12
|
+
|
|
13
|
+
scheduleMicroTaskSimple(() => {
|
|
14
|
+
values.push("micro")
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
expect(values).toEqual([])
|
|
18
|
+
|
|
19
|
+
await Promise.resolve()
|
|
20
|
+
|
|
21
|
+
expect(values).toEqual(["micro"])
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test("scheduleMacroTaskSimple schedules a task in the macrotask queue", async () => {
|
|
25
|
+
vi.useFakeTimers()
|
|
26
|
+
const values: string[] = []
|
|
27
|
+
|
|
28
|
+
scheduleMacroTaskSimple(() => {
|
|
29
|
+
values.push("macro")
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
expect(values).toEqual([])
|
|
33
|
+
|
|
34
|
+
await vi.runAllTimersAsync()
|
|
35
|
+
|
|
36
|
+
expect(values).toEqual(["macro"])
|
|
37
|
+
vi.useRealTimers()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test("scheduleMicroTask schedules async-capable tasks in the microtask queue", async () => {
|
|
41
|
+
const values: string[] = []
|
|
42
|
+
|
|
43
|
+
scheduleMicroTask({
|
|
44
|
+
task: () => {
|
|
45
|
+
values.push("micro")
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
expect(values).toEqual([])
|
|
50
|
+
|
|
51
|
+
await Promise.resolve()
|
|
52
|
+
await Promise.resolve()
|
|
53
|
+
|
|
54
|
+
expect(values).toEqual(["micro"])
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test("scheduleMacroTask schedules async-capable tasks with timeout", async () => {
|
|
58
|
+
vi.useFakeTimers()
|
|
59
|
+
const values: string[] = []
|
|
60
|
+
|
|
61
|
+
scheduleMacroTask({
|
|
62
|
+
timeout: 25,
|
|
63
|
+
task: () => {
|
|
64
|
+
values.push("macro")
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
await vi.advanceTimersByTimeAsync(24)
|
|
69
|
+
expect(values).toEqual([])
|
|
70
|
+
|
|
71
|
+
await vi.advanceTimersByTimeAsync(1)
|
|
72
|
+
expect(values).toEqual(["macro"])
|
|
73
|
+
vi.useRealTimers()
|
|
74
|
+
})
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { expect, test } from "vitest"
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
streamConsumeInMacroTask,
|
|
5
|
+
streamConsumeInMicroTask,
|
|
6
|
+
streamFromArrayEager,
|
|
7
|
+
streamFromArrayLazy,
|
|
8
|
+
streamTransformInMacroTask,
|
|
8
9
|
} from "#Source/basic/index.ts"
|
|
9
10
|
|
|
10
|
-
test("
|
|
11
|
+
test("streamFromArrayEager creates a readable stream from array values", async () => {
|
|
11
12
|
const source = [1, 2, 3]
|
|
12
13
|
const values: number[] = []
|
|
13
14
|
|
|
14
|
-
const stream =
|
|
15
|
+
const stream = streamFromArrayEager(source)
|
|
15
16
|
for await (const value of stream) {
|
|
16
17
|
values.push(value)
|
|
17
18
|
}
|
|
@@ -19,18 +20,38 @@ test("streamFromArray creates a readable stream from array values", async () =>
|
|
|
19
20
|
expect(values).toEqual(source)
|
|
20
21
|
})
|
|
21
22
|
|
|
22
|
-
test("
|
|
23
|
+
test("streamFromArrayLazy creates a lazily consumed readable stream", async () => {
|
|
24
|
+
const source = [1, 2, 3]
|
|
25
|
+
const stream = streamFromArrayLazy(source)
|
|
26
|
+
const reader = stream.getReader()
|
|
27
|
+
|
|
28
|
+
const firstChunk = await reader.read()
|
|
29
|
+
const secondChunk = await reader.read()
|
|
30
|
+
const thirdChunk = await reader.read()
|
|
31
|
+
const doneChunk = await reader.read()
|
|
32
|
+
|
|
33
|
+
expect(firstChunk).toEqual({ done: false, value: 1 })
|
|
34
|
+
expect(secondChunk).toEqual({ done: false, value: 2 })
|
|
35
|
+
expect(thirdChunk).toEqual({ done: false, value: 3 })
|
|
36
|
+
expect(doneChunk).toEqual({ done: true, value: undefined })
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test("streamConsumeInMicroTask consumes stream and handles callback errors", async () => {
|
|
23
40
|
const consumed: number[] = []
|
|
24
41
|
let doneCalled = false
|
|
25
42
|
|
|
26
|
-
await
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
await new Promise<void>((resolve, reject) => {
|
|
44
|
+
streamConsumeInMicroTask<number>({
|
|
45
|
+
readableStream: streamFromArrayEager([1, 2, 3]),
|
|
46
|
+
onValue: (chunk) => {
|
|
47
|
+
consumed.push(chunk)
|
|
48
|
+
},
|
|
49
|
+
onDone: () => {
|
|
50
|
+
doneCalled = true
|
|
51
|
+
resolve()
|
|
52
|
+
},
|
|
53
|
+
onError: reject,
|
|
54
|
+
})
|
|
34
55
|
})
|
|
35
56
|
|
|
36
57
|
expect(consumed).toEqual([1, 2, 3])
|
|
@@ -38,30 +59,33 @@ test("streamConsumeInSyncMacroTask consumes stream and handles callback errors",
|
|
|
38
59
|
|
|
39
60
|
let errorMessage = ""
|
|
40
61
|
let doneCalledInErrorCase = false
|
|
41
|
-
await
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
62
|
+
await new Promise<void>((resolve) => {
|
|
63
|
+
streamConsumeInMicroTask<number>({
|
|
64
|
+
readableStream: streamFromArrayEager([1, 2]),
|
|
65
|
+
onValue: (chunk) => {
|
|
66
|
+
if (chunk === 2) {
|
|
67
|
+
throw new Error("boom")
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
onDone: () => {
|
|
71
|
+
doneCalledInErrorCase = true
|
|
72
|
+
},
|
|
73
|
+
onError: (error) => {
|
|
74
|
+
errorMessage = error.message
|
|
75
|
+
resolve()
|
|
76
|
+
},
|
|
77
|
+
})
|
|
54
78
|
})
|
|
55
79
|
|
|
56
80
|
expect(errorMessage).toContain("boom")
|
|
57
81
|
expect(doneCalledInErrorCase).toBe(false)
|
|
58
82
|
})
|
|
59
83
|
|
|
60
|
-
test("
|
|
84
|
+
test("streamConsumeInMacroTask consumes stream and forwards callback failures", async () => {
|
|
61
85
|
const consumed: number[] = []
|
|
62
86
|
await new Promise<void>((resolve, reject) => {
|
|
63
|
-
|
|
64
|
-
readableStream:
|
|
87
|
+
streamConsumeInMacroTask<number>({
|
|
88
|
+
readableStream: streamFromArrayEager([1, 2, 3]),
|
|
65
89
|
onValue: (chunk) => {
|
|
66
90
|
consumed.push(chunk)
|
|
67
91
|
},
|
|
@@ -78,8 +102,8 @@ test("streamConsumeInAsyncMacroTask consumes stream and forwards callback failur
|
|
|
78
102
|
|
|
79
103
|
let errorMessage = ""
|
|
80
104
|
await new Promise<void>((resolve, reject) => {
|
|
81
|
-
|
|
82
|
-
readableStream:
|
|
105
|
+
streamConsumeInMacroTask<number>({
|
|
106
|
+
readableStream: streamFromArrayEager([1]),
|
|
83
107
|
onValue: () => {
|
|
84
108
|
throw new Error("async-boom")
|
|
85
109
|
},
|
|
@@ -96,13 +120,13 @@ test("streamConsumeInAsyncMacroTask consumes stream and forwards callback failur
|
|
|
96
120
|
expect(errorMessage).toContain("async-boom")
|
|
97
121
|
})
|
|
98
122
|
|
|
99
|
-
test("
|
|
123
|
+
test("streamTransformInMacroTask transforms values and handles invalid inputs/errors", async () => {
|
|
100
124
|
expect(() => {
|
|
101
|
-
|
|
125
|
+
streamTransformInMacroTask<number, number>({})
|
|
102
126
|
}).toThrow("Either readableStream or reader must be provided")
|
|
103
127
|
|
|
104
|
-
const transformed =
|
|
105
|
-
readableStream:
|
|
128
|
+
const transformed = streamTransformInMacroTask<number, number>({
|
|
129
|
+
readableStream: streamFromArrayEager([1, 2, 3]),
|
|
106
130
|
onChunk: (chunk, controller) => {
|
|
107
131
|
if (chunk.done === true) {
|
|
108
132
|
controller.close()
|
|
@@ -117,4 +141,33 @@ test("streamTransformInAsyncMacroTask transforms values and handles invalid inpu
|
|
|
117
141
|
values.push(value)
|
|
118
142
|
}
|
|
119
143
|
expect(values).toEqual([10, 20, 30])
|
|
144
|
+
|
|
145
|
+
const reader = streamFromArrayEager([4]).getReader()
|
|
146
|
+
const transformedFromReader = streamTransformInMacroTask<number, number>({
|
|
147
|
+
reader,
|
|
148
|
+
onChunk: (chunk, controller) => {
|
|
149
|
+
if (chunk.done === true) {
|
|
150
|
+
controller.close()
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
controller.enqueue(chunk.value + 1)
|
|
154
|
+
},
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
expect(await Array.fromAsync(transformedFromReader)).toEqual([5])
|
|
158
|
+
|
|
159
|
+
let refinedError: Error | undefined
|
|
160
|
+
const errored = streamTransformInMacroTask<number, number>({
|
|
161
|
+
readableStream: streamFromArrayEager([1]),
|
|
162
|
+
onChunk: () => {
|
|
163
|
+
throw new Error("transform-boom")
|
|
164
|
+
},
|
|
165
|
+
onError: (error) => {
|
|
166
|
+
refinedError = new Error(`wrapped:${error.message}`)
|
|
167
|
+
return refinedError
|
|
168
|
+
},
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
await expect(Array.fromAsync(errored)).rejects.toThrow("wrapped:Error: transform-boom")
|
|
172
|
+
expect(refinedError?.message).toBe("wrapped:Error: transform-boom")
|
|
120
173
|
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { generateApiKey, maskApiKey } from "#Source/credential/index.ts"
|
|
4
|
+
|
|
5
|
+
test("generateApiKey creates a stable sk-prefixed key shape", () => {
|
|
6
|
+
vi.spyOn(crypto, "getRandomValues").mockImplementation(buffer => {
|
|
7
|
+
if (buffer === null) {
|
|
8
|
+
return buffer
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
12
|
+
const target = buffer as Uint8Array
|
|
13
|
+
target.forEach((_, index) => {
|
|
14
|
+
target[index] = index
|
|
15
|
+
})
|
|
16
|
+
return buffer
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const apiKey = generateApiKey({})
|
|
20
|
+
const customApiKey = generateApiKey({ prefix: "pk_", alphabet: "ABC123", bodyLength: 6 })
|
|
21
|
+
|
|
22
|
+
expect(apiKey.startsWith("sk-")).toBe(true)
|
|
23
|
+
expect(apiKey).toHaveLength(51)
|
|
24
|
+
expect(apiKey).toMatch(/^sk-[0-9A-Za-z]{48}$/)
|
|
25
|
+
expect(customApiKey).toBe("pk_ABC123")
|
|
26
|
+
expect(() => generateApiKey({ alphabet: "", bodyLength: 6 })).toThrow("Expected alphabet to contain at least one character")
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test("maskApiKey hides the middle of a valid key and rejects malformed input", () => {
|
|
30
|
+
const apiKey = `sk-${"ab"}${"0".repeat(44)}yz`
|
|
31
|
+
const customOptions = { prefix: "pk_", alphabet: "ABC123", bodyLength: 6 } as const
|
|
32
|
+
|
|
33
|
+
expect(maskApiKey(apiKey)).toBe("sk-ab********************************************yz")
|
|
34
|
+
expect(maskApiKey("pk_ABC123", customOptions)).toBe("pk_AB**23")
|
|
35
|
+
expect(() => maskApiKey("not-an-api-key")).toThrow("Invalid API key format")
|
|
36
|
+
expect(() => maskApiKey(`sk-${"#".repeat(48)}`)).toThrow("Invalid API key format")
|
|
37
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { formatBearerCredential, isBearerCredential, parseBearerCredential } from "#Source/credential/index.ts"
|
|
4
|
+
|
|
5
|
+
test("isBearerCredential recognizes bearer credential strings", () => {
|
|
6
|
+
expect(isBearerCredential("Bearer token-value")).toBe(true)
|
|
7
|
+
expect(isBearerCredential("bearer token-value")).toBe(true)
|
|
8
|
+
expect(isBearerCredential("Basic token-value")).toBe(false)
|
|
9
|
+
expect(isBearerCredential("token-value")).toBe(false)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test("formatBearerCredential prefixes a raw token with the Bearer scheme", () => {
|
|
13
|
+
expect(formatBearerCredential("token-value")).toBe("Bearer token-value")
|
|
14
|
+
expect(formatBearerCredential(" token-value ")).toBe("Bearer token-value")
|
|
15
|
+
expect(() => formatBearerCredential(" ")).toThrow("Expected token to contain at least one non-whitespace character")
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test("parseBearerCredential extracts the raw token only from bearer credentials", () => {
|
|
19
|
+
expect(parseBearerCredential("Bearer token-value")).toBe("token-value")
|
|
20
|
+
expect(parseBearerCredential("bearer token-value")).toBe("token-value")
|
|
21
|
+
expect(parseBearerCredential("Basic token-value")).toBeUndefined()
|
|
22
|
+
expect(parseBearerCredential(undefined)).toBeUndefined()
|
|
23
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { formatBearerCredential, JsonWebToken } from "#Source/credential/index.ts"
|
|
4
|
+
|
|
5
|
+
test("JsonWebToken.signToken creates a compact JWT string", async () => {
|
|
6
|
+
const jwt = new JsonWebToken({ secret: "credential-secret", expiresIn: 3_600 })
|
|
7
|
+
|
|
8
|
+
const token = await jwt.signToken("user-1")
|
|
9
|
+
|
|
10
|
+
expect(token.split(".")).toHaveLength(3)
|
|
11
|
+
expect(typeof token).toBe("string")
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test("JsonWebToken.parseToken returns userId for valid tokens and undefined for invalid input", async () => {
|
|
15
|
+
const jwt = new JsonWebToken({ secret: "credential-secret", expiresIn: 3_600 })
|
|
16
|
+
const token = await jwt.signToken("user-1")
|
|
17
|
+
|
|
18
|
+
await expect(jwt.parseToken(token)).resolves.toBe("user-1")
|
|
19
|
+
await expect(jwt.parseToken(formatBearerCredential(token))).resolves.toBeUndefined()
|
|
20
|
+
await expect(jwt.parseToken(undefined)).resolves.toBeUndefined()
|
|
21
|
+
await expect(jwt.parseToken("broken.token.value")).resolves.toBeUndefined()
|
|
22
|
+
await expect(new JsonWebToken({ secret: "other-secret", expiresIn: 3_600 }).parseToken(token)).resolves.toBeUndefined()
|
|
23
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { Password } from "#Source/credential/index.ts"
|
|
4
|
+
|
|
5
|
+
test("Password.generateSalt returns a hexadecimal salt string of the requested byte length", () => {
|
|
6
|
+
vi.spyOn(crypto, "getRandomValues").mockImplementation(buffer => {
|
|
7
|
+
if (buffer === null) {
|
|
8
|
+
return buffer
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
12
|
+
const target = buffer as Uint8Array
|
|
13
|
+
target.fill(0xAB)
|
|
14
|
+
return buffer
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const salt = Password.generateSalt(8)
|
|
18
|
+
|
|
19
|
+
expect(salt).toBe("abababababababab")
|
|
20
|
+
expect(salt).toHaveLength(16)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test("Password.hashPasswordWithSalt derives a stable PBKDF2-SHA-512 hash for the same input", async () => {
|
|
24
|
+
const salt = "00112233445566778899AABBCCDDEEFF"
|
|
25
|
+
const hash1 = await Password.hashPasswordWithSalt("secret", salt)
|
|
26
|
+
const hash2 = await Password.hashPasswordWithSalt("secret", salt)
|
|
27
|
+
const hash3 = await Password.hashPasswordWithSalt("other", salt)
|
|
28
|
+
|
|
29
|
+
expect(hash1).toHaveLength(128)
|
|
30
|
+
expect(hash1).toMatch(/^[0-9A-F]+$/i)
|
|
31
|
+
expect(hash1).toBe(hash2)
|
|
32
|
+
expect(hash1).not.toBe(hash3)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test("Password.comparePassword distinguishes matching and mismatching passwords", async () => {
|
|
36
|
+
const salt = "00112233445566778899AABBCCDDEEFF"
|
|
37
|
+
const hash = await Password.hashPasswordWithSalt("secret", salt)
|
|
38
|
+
|
|
39
|
+
await expect(Password.comparePassword("secret", hash, salt)).resolves.toBe(true)
|
|
40
|
+
await expect(Password.comparePassword("other", hash, salt)).resolves.toBe(false)
|
|
41
|
+
})
|