ai-test-kit 0.0.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.
@@ -0,0 +1,266 @@
1
+ import { t as tokenize } from "../tokenize-C-Zp26iY.mjs";
2
+
3
+ //#region src/ui/parts.ts
4
+ /**
5
+ * Builds the {@link UIParts} namespace bound to a message's `DATA` and `TOOLS` types. The runtime is
6
+ * identical for every binding; the type parameters only sharpen `data` and `tool`. Use the top-level
7
+ * {@link UIParts} for the loose default, or `fromUIMessage` to bind.
8
+ */
9
+ const createUIParts = () => ({
10
+ text: (text, opts) => ({
11
+ type: "text",
12
+ text,
13
+ ...opts
14
+ }),
15
+ reasoning: (text, opts) => ({
16
+ type: "reasoning",
17
+ text,
18
+ ...opts
19
+ }),
20
+ sourceUrl: (args) => ({
21
+ type: "source-url",
22
+ ...args
23
+ }),
24
+ sourceDocument: (args) => ({
25
+ type: "source-document",
26
+ ...args
27
+ }),
28
+ file: (args) => ({
29
+ type: "file",
30
+ ...args
31
+ }),
32
+ stepStart: () => ({ type: "step-start" }),
33
+ data: (name, data, opts = {}) => ({
34
+ type: `data-${name}`,
35
+ data,
36
+ ...opts
37
+ }),
38
+ tool: (name, invocation) => ({
39
+ type: `tool-${name}`,
40
+ ...invocation
41
+ }),
42
+ dynamicTool: (args) => ({
43
+ type: "dynamic-tool",
44
+ ...args
45
+ })
46
+ });
47
+ /** Builders for `UIMessagePart`s, with loose default types. Use `fromUIMessage` to bind to a message type. */
48
+ const UIParts = createUIParts();
49
+
50
+ //#endregion
51
+ //#region src/ui/chunks.ts
52
+ /**
53
+ * Builds the {@link UIChunks} namespace bound to a message's `METADATA` and `DATA` types. The runtime
54
+ * is identical for every binding; the type parameters only sharpen `data`, `start`, `finish`, and
55
+ * `messageMetadata`. Use the top-level {@link UIChunks} for the loose default, or `fromUIMessage` to bind.
56
+ */
57
+ const createUIChunks = () => ({
58
+ textStart: (args) => ({
59
+ type: "text-start",
60
+ ...args
61
+ }),
62
+ textDelta: (args) => ({
63
+ type: "text-delta",
64
+ ...args
65
+ }),
66
+ textEnd: (args) => ({
67
+ type: "text-end",
68
+ ...args
69
+ }),
70
+ reasoningStart: (args) => ({
71
+ type: "reasoning-start",
72
+ ...args
73
+ }),
74
+ reasoningDelta: (args) => ({
75
+ type: "reasoning-delta",
76
+ ...args
77
+ }),
78
+ reasoningEnd: (args) => ({
79
+ type: "reasoning-end",
80
+ ...args
81
+ }),
82
+ error: (errorText) => ({
83
+ type: "error",
84
+ errorText
85
+ }),
86
+ toolInputStart: (args) => ({
87
+ type: "tool-input-start",
88
+ ...args
89
+ }),
90
+ toolInputDelta: (args) => ({
91
+ type: "tool-input-delta",
92
+ ...args
93
+ }),
94
+ toolInputAvailable: (args) => ({
95
+ type: "tool-input-available",
96
+ ...args
97
+ }),
98
+ toolInputError: (args) => ({
99
+ type: "tool-input-error",
100
+ ...args
101
+ }),
102
+ toolApprovalRequest: (args) => ({
103
+ type: "tool-approval-request",
104
+ ...args
105
+ }),
106
+ toolOutputAvailable: (args) => ({
107
+ type: "tool-output-available",
108
+ ...args
109
+ }),
110
+ toolOutputError: (args) => ({
111
+ type: "tool-output-error",
112
+ ...args
113
+ }),
114
+ toolOutputDenied: (args) => ({
115
+ type: "tool-output-denied",
116
+ ...args
117
+ }),
118
+ sourceUrl: (args) => ({
119
+ type: "source-url",
120
+ ...args
121
+ }),
122
+ sourceDocument: (args) => ({
123
+ type: "source-document",
124
+ ...args
125
+ }),
126
+ file: (args) => ({
127
+ type: "file",
128
+ ...args
129
+ }),
130
+ data: (name, data, opts = {}) => ({
131
+ type: `data-${name}`,
132
+ data,
133
+ ...opts
134
+ }),
135
+ startStep: () => ({ type: "start-step" }),
136
+ finishStep: () => ({ type: "finish-step" }),
137
+ start: (args) => ({
138
+ type: "start",
139
+ ...args
140
+ }),
141
+ finish: (args) => ({
142
+ type: "finish",
143
+ ...args
144
+ }),
145
+ abort: (args) => ({
146
+ type: "abort",
147
+ ...args
148
+ }),
149
+ messageMetadata: (messageMetadata) => ({
150
+ type: "message-metadata",
151
+ messageMetadata
152
+ }),
153
+ text: (text, { id = "1", length, separator } = {}) => [
154
+ {
155
+ type: "text-start",
156
+ id
157
+ },
158
+ ...tokenize(text, {
159
+ length,
160
+ separator
161
+ }).map((delta) => ({
162
+ type: "text-delta",
163
+ id,
164
+ delta
165
+ })),
166
+ {
167
+ type: "text-end",
168
+ id
169
+ }
170
+ ],
171
+ reasoning: (text, { id = "1", length, separator } = {}) => [
172
+ {
173
+ type: "reasoning-start",
174
+ id
175
+ },
176
+ ...tokenize(text, {
177
+ length,
178
+ separator
179
+ }).map((delta) => ({
180
+ type: "reasoning-delta",
181
+ id,
182
+ delta
183
+ })),
184
+ {
185
+ type: "reasoning-end",
186
+ id
187
+ }
188
+ ],
189
+ toolInput: (args) => [
190
+ {
191
+ type: "tool-input-start",
192
+ toolCallId: args.toolCallId,
193
+ toolName: args.toolName
194
+ },
195
+ ...tokenize(JSON.stringify(args.input), { length: args.length }).map((inputTextDelta) => ({
196
+ type: "tool-input-delta",
197
+ toolCallId: args.toolCallId,
198
+ inputTextDelta
199
+ })),
200
+ {
201
+ type: "tool-input-available",
202
+ toolCallId: args.toolCallId,
203
+ toolName: args.toolName,
204
+ input: args.input
205
+ }
206
+ ]
207
+ });
208
+ /** Builders for `UIMessageChunk`s, with loose default types. Use `fromUIMessage` to bind to a message type. */
209
+ const UIChunks = createUIChunks();
210
+
211
+ //#endregion
212
+ //#region src/ui/message.ts
213
+ /** Monotonic counter backing the auto-generated message ids. */
214
+ let messageCounter = 0;
215
+ /** Returns the next unique auto-generated message id. */
216
+ const nextMessageId = () => {
217
+ messageCounter += 1;
218
+ return `mock-message-${messageCounter}`;
219
+ };
220
+ /**
221
+ * Builds the {@link UIMessages} namespace bound to a message's `METADATA`, `DATA`, and `TOOLS` types.
222
+ * Each role helper accepts a `string` shortcut (becomes a single text part) or a full array of parts.
223
+ * Use the top-level {@link UIMessages} for the loose default, or `fromUIMessage` to bind.
224
+ */
225
+ const createUIMessages = () => {
226
+ /** Normalizes a string shortcut into a single text part, or passes an array of parts through. */
227
+ const toParts = (content) => typeof content === "string" ? [{
228
+ type: "text",
229
+ text: content
230
+ }] : content;
231
+ /** Assembles a message of the given role from content and options. */
232
+ const build = (role, content, opts) => ({
233
+ id: opts.id ?? nextMessageId(),
234
+ role,
235
+ parts: toParts(content),
236
+ ...opts.metadata !== void 0 ? { metadata: opts.metadata } : {}
237
+ });
238
+ return {
239
+ user: (content, opts = {}) => build("user", content, opts),
240
+ assistant: (content, opts = {}) => build("assistant", content, opts),
241
+ system: (content, opts = {}) => build("system", content, opts)
242
+ };
243
+ };
244
+ /** Builders for `UIMessage`s, with loose default types. Use `fromUIMessage` to bind to a message type. */
245
+ const UIMessages = createUIMessages();
246
+
247
+ //#endregion
248
+ //#region src/ui/from-ui-message.ts
249
+ /**
250
+ * Binds the UI builders to a concrete `UIMessage` type, so `data`/`tool`/`messageMetadata` and message
251
+ * metadata infer their names and payloads from that type. Returns typed `UIParts`, `UIChunks`, and
252
+ * `UIMessages` namespaces; for loose, untyped builders use the top-level exports instead.
253
+ *
254
+ * @example
255
+ * const { UIParts, UIChunks, UIMessages } = fromUIMessage<MyUIMessage>();
256
+ * UIChunks.data('weather', { city: 'Tokyo' }); // name and payload typed
257
+ * UIMessages.assistant([UIParts.text('hi')]);
258
+ */
259
+ const fromUIMessage = () => ({
260
+ UIParts: createUIParts(),
261
+ UIChunks: createUIChunks(),
262
+ UIMessages: createUIMessages()
263
+ });
264
+
265
+ //#endregion
266
+ export { UIChunks, UIMessages, UIParts, createUIChunks, createUIMessages, createUIParts, fromUIMessage };
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "ai-test-kit",
3
+ "version": "0.0.1",
4
+ "description": "Test utilities for the AI SDK: mock models, content and stream-part builders, fully type-safe",
5
+ "keywords": [],
6
+ "license": "MIT",
7
+ "author": "Chris Cook",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/zirkelc/ai-test-kit.git"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "type": "module",
16
+ "types": "dist/index.d.mts",
17
+ "exports": {
18
+ ".": "./dist/index.mjs",
19
+ "./language": "./dist/language/index.mjs",
20
+ "./ui": "./dist/ui/index.mjs",
21
+ "./package.json": "./package.json"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "scripts": {
27
+ "prepublishOnly": "pnpm build",
28
+ "build": "tsdown",
29
+ "test": "vitest",
30
+ "lint": "oxlint --fix",
31
+ "lint:ci": "oxlint",
32
+ "format": "oxfmt --write",
33
+ "format:ci": "oxfmt --check",
34
+ "prepare": "husky"
35
+ },
36
+ "devDependencies": {
37
+ "@ai-sdk/provider": "^3.0.10",
38
+ "@ai-sdk/provider-utils": "^4.0.27",
39
+ "@arethetypeswrong/cli": "^0.18.2",
40
+ "@total-typescript/tsconfig": "^1.0.4",
41
+ "@types/node": "^25.2.3",
42
+ "ai": "^6.0.197",
43
+ "husky": "^9.1.7",
44
+ "lint-staged": "^16.2.7",
45
+ "oxfmt": "^0.30.0",
46
+ "oxlint": "^1.45.0",
47
+ "oxlint-tsgolint": "^0.11.5",
48
+ "pkg-pr-new": "^0.0.63",
49
+ "publint": "^0.3.17",
50
+ "tsdown": "^0.20.3",
51
+ "tsx": "^4.21.0",
52
+ "typescript": "^5.9.3",
53
+ "vitest": "^4.0.18"
54
+ },
55
+ "peerDependencies": {
56
+ "@ai-sdk/provider": "^3.0.0",
57
+ "@ai-sdk/provider-utils": "^4.0.0",
58
+ "ai": "5.x || 6.x",
59
+ "vitest": ">=3"
60
+ },
61
+ "lint-staged": {
62
+ "*.{js,ts,tsx,jsx}": [
63
+ "pnpm lint",
64
+ "pnpm format"
65
+ ],
66
+ "*.{json,jsonc}": [
67
+ "pnpm format"
68
+ ]
69
+ },
70
+ "packageManager": "pnpm@10.29.2"
71
+ }