@ai-sdk/xai 4.0.0-beta.4 → 4.0.0-beta.40
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 +311 -8
- package/README.md +2 -0
- package/dist/index.d.ts +126 -50
- package/dist/index.js +1169 -749
- package/dist/index.js.map +1 -1
- package/docs/01-xai.mdx +173 -381
- package/package.json +8 -10
- package/src/convert-to-xai-chat-messages.ts +22 -6
- package/src/convert-xai-chat-usage.ts +2 -2
- package/src/files/xai-files-api.ts +16 -0
- package/src/files/xai-files-options.ts +15 -0
- package/src/files/xai-files.ts +93 -0
- package/src/index.ts +1 -0
- package/src/map-xai-finish-reason.ts +2 -2
- package/src/responses/convert-to-xai-responses-input.ts +63 -8
- package/src/responses/convert-xai-responses-usage.ts +2 -2
- package/src/responses/map-xai-responses-finish-reason.ts +3 -2
- package/src/responses/xai-responses-api.ts +31 -1
- package/src/responses/xai-responses-language-model.ts +134 -48
- package/src/responses/xai-responses-options.ts +6 -0
- package/src/responses/xai-responses-prepare-tools.ts +6 -6
- package/src/xai-chat-language-model.ts +61 -25
- package/src/xai-chat-options.ts +3 -6
- package/src/xai-chat-prompt.ts +2 -1
- package/src/xai-image-model.ts +25 -8
- package/src/xai-image-settings.ts +0 -2
- package/src/xai-prepare-tools.ts +6 -6
- package/src/xai-provider.ts +34 -21
- package/src/xai-video-model.ts +127 -20
- package/src/xai-video-options.ts +136 -14
- package/dist/index.d.mts +0 -375
- package/dist/index.mjs +0 -3061
- package/dist/index.mjs.map +0 -1
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/xai",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.40",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"license": "Apache-2.0",
|
|
5
6
|
"sideEffects": false,
|
|
6
7
|
"main": "./dist/index.js",
|
|
7
|
-
"module": "./dist/index.mjs",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"files": [
|
|
10
10
|
"dist/**/*",
|
|
@@ -24,21 +24,21 @@
|
|
|
24
24
|
"./package.json": "./package.json",
|
|
25
25
|
".": {
|
|
26
26
|
"types": "./dist/index.d.ts",
|
|
27
|
-
"import": "./dist/index.
|
|
28
|
-
"
|
|
27
|
+
"import": "./dist/index.js",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@ai-sdk/
|
|
33
|
-
"@ai-sdk/
|
|
34
|
-
"@ai-sdk/provider-utils": "5.0.0-beta.
|
|
32
|
+
"@ai-sdk/openai-compatible": "3.0.0-beta.28",
|
|
33
|
+
"@ai-sdk/provider": "4.0.0-beta.12",
|
|
34
|
+
"@ai-sdk/provider-utils": "5.0.0-beta.23"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "20.17.24",
|
|
38
38
|
"tsup": "^8",
|
|
39
39
|
"typescript": "5.8.3",
|
|
40
40
|
"zod": "3.25.76",
|
|
41
|
-
"@ai-sdk/test-server": "2.0.0-beta.
|
|
41
|
+
"@ai-sdk/test-server": "2.0.0-beta.1",
|
|
42
42
|
"@vercel/ai-tsconfig": "0.0.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
@@ -65,9 +65,7 @@
|
|
|
65
65
|
"build": "pnpm clean && tsup --tsconfig tsconfig.build.json",
|
|
66
66
|
"build:watch": "pnpm clean && tsup --watch",
|
|
67
67
|
"clean": "del-cli dist docs *.tsbuildinfo",
|
|
68
|
-
"lint": "eslint \"./**/*.ts*\"",
|
|
69
68
|
"type-check": "tsc --build",
|
|
70
|
-
"prettier-check": "prettier --check \"./**/*.ts*\"",
|
|
71
69
|
"test": "pnpm test:node && pnpm test:edge",
|
|
72
70
|
"test:update": "pnpm test:node -u",
|
|
73
71
|
"test:watch": "vitest --config vitest.node.config.js",
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
SharedV4Warning,
|
|
3
|
+
LanguageModelV4Prompt,
|
|
4
4
|
UnsupportedFunctionalityError,
|
|
5
5
|
} from '@ai-sdk/provider';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
convertToBase64,
|
|
8
|
+
isProviderReference,
|
|
9
|
+
resolveProviderReference,
|
|
10
|
+
} from '@ai-sdk/provider-utils';
|
|
7
11
|
import { XaiChatPrompt } from './xai-chat-prompt';
|
|
8
12
|
|
|
9
|
-
export function convertToXaiChatMessages(prompt:
|
|
13
|
+
export function convertToXaiChatMessages(prompt: LanguageModelV4Prompt): {
|
|
10
14
|
messages: XaiChatPrompt;
|
|
11
|
-
warnings: Array<
|
|
15
|
+
warnings: Array<SharedV4Warning>;
|
|
12
16
|
} {
|
|
13
17
|
const messages: XaiChatPrompt = [];
|
|
14
|
-
const warnings: Array<
|
|
18
|
+
const warnings: Array<SharedV4Warning> = [];
|
|
15
19
|
|
|
16
20
|
for (const { role, content } of prompt) {
|
|
17
21
|
switch (role) {
|
|
@@ -34,6 +38,18 @@ export function convertToXaiChatMessages(prompt: LanguageModelV3Prompt): {
|
|
|
34
38
|
return { type: 'text', text: part.text };
|
|
35
39
|
}
|
|
36
40
|
case 'file': {
|
|
41
|
+
if (isProviderReference(part.data)) {
|
|
42
|
+
return {
|
|
43
|
+
type: 'file',
|
|
44
|
+
file: {
|
|
45
|
+
file_id: resolveProviderReference({
|
|
46
|
+
reference: part.data,
|
|
47
|
+
provider: 'xai',
|
|
48
|
+
}),
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
37
53
|
if (part.mediaType.startsWith('image/')) {
|
|
38
54
|
const mediaType =
|
|
39
55
|
part.mediaType === 'image/*'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LanguageModelV4Usage } from '@ai-sdk/provider';
|
|
2
2
|
import { XaiChatUsage } from './xai-chat-language-model';
|
|
3
3
|
|
|
4
|
-
export function convertXaiChatUsage(usage: XaiChatUsage):
|
|
4
|
+
export function convertXaiChatUsage(usage: XaiChatUsage): LanguageModelV4Usage {
|
|
5
5
|
const cacheReadTokens = usage.prompt_tokens_details?.cached_tokens ?? 0;
|
|
6
6
|
const reasoningTokens =
|
|
7
7
|
usage.completion_tokens_details?.reasoning_tokens ?? 0;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { lazySchema, zodSchema } from '@ai-sdk/provider-utils';
|
|
2
|
+
import { z } from 'zod/v4';
|
|
3
|
+
|
|
4
|
+
export const xaiFilesResponseSchema = lazySchema(() =>
|
|
5
|
+
zodSchema(
|
|
6
|
+
z.object({
|
|
7
|
+
id: z.string(),
|
|
8
|
+
object: z.string().nullish(),
|
|
9
|
+
bytes: z.number().nullish(),
|
|
10
|
+
created_at: z.number().nullish(),
|
|
11
|
+
filename: z.string().nullish(),
|
|
12
|
+
purpose: z.string().nullish(),
|
|
13
|
+
status: z.string().nullish(),
|
|
14
|
+
}),
|
|
15
|
+
),
|
|
16
|
+
);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { InferSchema, lazySchema, zodSchema } from '@ai-sdk/provider-utils';
|
|
2
|
+
import { z } from 'zod/v4';
|
|
3
|
+
|
|
4
|
+
export const xaiFilesOptionsSchema = lazySchema(() =>
|
|
5
|
+
zodSchema(
|
|
6
|
+
z
|
|
7
|
+
.object({
|
|
8
|
+
teamId: z.string().optional(),
|
|
9
|
+
filePath: z.string().optional(),
|
|
10
|
+
})
|
|
11
|
+
.passthrough(),
|
|
12
|
+
),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export type XaiFilesOptions = InferSchema<typeof xaiFilesOptionsSchema>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FilesV4,
|
|
3
|
+
FilesV4UploadFileCallOptions,
|
|
4
|
+
FilesV4UploadFileResult,
|
|
5
|
+
} from '@ai-sdk/provider';
|
|
6
|
+
import {
|
|
7
|
+
combineHeaders,
|
|
8
|
+
convertBase64ToUint8Array,
|
|
9
|
+
createJsonResponseHandler,
|
|
10
|
+
FetchFunction,
|
|
11
|
+
parseProviderOptions,
|
|
12
|
+
postFormDataToApi,
|
|
13
|
+
} from '@ai-sdk/provider-utils';
|
|
14
|
+
import { xaiFailedResponseHandler } from '../xai-error';
|
|
15
|
+
import { xaiFilesResponseSchema } from './xai-files-api';
|
|
16
|
+
import { xaiFilesOptionsSchema, XaiFilesOptions } from './xai-files-options';
|
|
17
|
+
|
|
18
|
+
interface XaiFilesConfig {
|
|
19
|
+
provider: string;
|
|
20
|
+
baseURL: string | undefined;
|
|
21
|
+
headers: () => Record<string, string | undefined>;
|
|
22
|
+
fetch?: FetchFunction;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class XaiFiles implements FilesV4 {
|
|
26
|
+
readonly specificationVersion = 'v4';
|
|
27
|
+
|
|
28
|
+
get provider(): string {
|
|
29
|
+
return this.config.provider;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
constructor(private readonly config: XaiFilesConfig) {}
|
|
33
|
+
|
|
34
|
+
async uploadFile({
|
|
35
|
+
data,
|
|
36
|
+
mediaType,
|
|
37
|
+
filename,
|
|
38
|
+
providerOptions,
|
|
39
|
+
}: FilesV4UploadFileCallOptions): Promise<FilesV4UploadFileResult> {
|
|
40
|
+
const xaiOptions = (await parseProviderOptions({
|
|
41
|
+
provider: 'xai',
|
|
42
|
+
providerOptions,
|
|
43
|
+
schema: xaiFilesOptionsSchema,
|
|
44
|
+
})) as XaiFilesOptions | undefined;
|
|
45
|
+
|
|
46
|
+
const fileBytes =
|
|
47
|
+
data instanceof Uint8Array ? data : convertBase64ToUint8Array(data);
|
|
48
|
+
|
|
49
|
+
const blob = new Blob([fileBytes], {
|
|
50
|
+
type: mediaType,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const formData = new FormData();
|
|
54
|
+
if (filename != null) {
|
|
55
|
+
formData.append('file', blob, filename);
|
|
56
|
+
} else {
|
|
57
|
+
formData.append('file', blob);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (xaiOptions?.teamId != null) {
|
|
61
|
+
formData.append('team_id', xaiOptions.teamId);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { value: response } = await postFormDataToApi({
|
|
65
|
+
url: `${this.config.baseURL}/files`,
|
|
66
|
+
headers: combineHeaders(this.config.headers()),
|
|
67
|
+
formData,
|
|
68
|
+
failedResponseHandler: xaiFailedResponseHandler,
|
|
69
|
+
successfulResponseHandler: createJsonResponseHandler(
|
|
70
|
+
xaiFilesResponseSchema,
|
|
71
|
+
),
|
|
72
|
+
fetch: this.config.fetch,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
warnings: [],
|
|
77
|
+
providerReference: { xai: response.id },
|
|
78
|
+
...((response.filename ?? filename)
|
|
79
|
+
? { filename: response.filename ?? filename }
|
|
80
|
+
: {}),
|
|
81
|
+
...(mediaType != null ? { mediaType } : {}),
|
|
82
|
+
providerMetadata: {
|
|
83
|
+
xai: {
|
|
84
|
+
...(response.filename != null ? { filename: response.filename } : {}),
|
|
85
|
+
...(response.bytes != null ? { bytes: response.bytes } : {}),
|
|
86
|
+
...(response.created_at != null
|
|
87
|
+
? { createdAt: response.created_at }
|
|
88
|
+
: {}),
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ export type {
|
|
|
20
20
|
/** @deprecated Use `XaiVideoModelOptions` instead. */
|
|
21
21
|
XaiVideoModelOptions as XaiVideoProviderOptions,
|
|
22
22
|
} from './xai-video-options';
|
|
23
|
+
export type { XaiFilesOptions } from './files/xai-files-options';
|
|
23
24
|
export { createXai, xai } from './xai-provider';
|
|
24
25
|
export type { XaiProvider, XaiProviderSettings } from './xai-provider';
|
|
25
26
|
export {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LanguageModelV4FinishReason } from '@ai-sdk/provider';
|
|
2
2
|
|
|
3
3
|
export function mapXaiFinishReason(
|
|
4
4
|
finishReason: string | null | undefined,
|
|
5
|
-
):
|
|
5
|
+
): LanguageModelV4FinishReason['unified'] {
|
|
6
6
|
switch (finishReason) {
|
|
7
7
|
case 'stop':
|
|
8
8
|
return 'stop';
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
SharedV4Warning,
|
|
3
|
+
LanguageModelV4Message,
|
|
4
4
|
UnsupportedFunctionalityError,
|
|
5
5
|
} from '@ai-sdk/provider';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
convertToBase64,
|
|
8
|
+
isProviderReference,
|
|
9
|
+
resolveProviderReference,
|
|
10
|
+
} from '@ai-sdk/provider-utils';
|
|
7
11
|
import {
|
|
8
12
|
XaiResponsesInput,
|
|
9
13
|
XaiResponsesUserMessageContentPart,
|
|
@@ -12,14 +16,14 @@ import {
|
|
|
12
16
|
export async function convertToXaiResponsesInput({
|
|
13
17
|
prompt,
|
|
14
18
|
}: {
|
|
15
|
-
prompt:
|
|
19
|
+
prompt: LanguageModelV4Message[];
|
|
16
20
|
store?: boolean;
|
|
17
21
|
}): Promise<{
|
|
18
22
|
input: XaiResponsesInput;
|
|
19
|
-
inputWarnings:
|
|
23
|
+
inputWarnings: SharedV4Warning[];
|
|
20
24
|
}> {
|
|
21
25
|
const input: XaiResponsesInput = [];
|
|
22
|
-
const inputWarnings:
|
|
26
|
+
const inputWarnings: SharedV4Warning[] = [];
|
|
23
27
|
|
|
24
28
|
for (const message of prompt) {
|
|
25
29
|
switch (message.role) {
|
|
@@ -42,7 +46,15 @@ export async function convertToXaiResponsesInput({
|
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
case 'file': {
|
|
45
|
-
if (block.
|
|
49
|
+
if (isProviderReference(block.data)) {
|
|
50
|
+
contentParts.push({
|
|
51
|
+
type: 'input_file',
|
|
52
|
+
file_id: resolveProviderReference({
|
|
53
|
+
reference: block.data,
|
|
54
|
+
provider: 'xai',
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
57
|
+
} else if (block.mediaType.startsWith('image/')) {
|
|
46
58
|
const mediaType =
|
|
47
59
|
block.mediaType === 'image/*'
|
|
48
60
|
? 'image/jpeg'
|
|
@@ -123,7 +135,50 @@ export async function convertToXaiResponsesInput({
|
|
|
123
135
|
break;
|
|
124
136
|
}
|
|
125
137
|
|
|
126
|
-
case 'reasoning':
|
|
138
|
+
case 'reasoning': {
|
|
139
|
+
const itemId =
|
|
140
|
+
typeof part.providerOptions?.xai?.itemId === 'string'
|
|
141
|
+
? part.providerOptions.xai.itemId
|
|
142
|
+
: undefined;
|
|
143
|
+
const encryptedContent =
|
|
144
|
+
typeof part.providerOptions?.xai?.reasoningEncryptedContent ===
|
|
145
|
+
'string'
|
|
146
|
+
? part.providerOptions.xai.reasoningEncryptedContent
|
|
147
|
+
: undefined;
|
|
148
|
+
|
|
149
|
+
if (itemId != null || encryptedContent != null) {
|
|
150
|
+
const summaryParts: Array<{
|
|
151
|
+
type: 'summary_text';
|
|
152
|
+
text: string;
|
|
153
|
+
}> = [];
|
|
154
|
+
if (part.text.length > 0) {
|
|
155
|
+
summaryParts.push({
|
|
156
|
+
type: 'summary_text',
|
|
157
|
+
text: part.text,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
input.push({
|
|
162
|
+
type: 'reasoning',
|
|
163
|
+
id: itemId ?? '',
|
|
164
|
+
summary: summaryParts,
|
|
165
|
+
status: 'completed',
|
|
166
|
+
...(encryptedContent != null && {
|
|
167
|
+
encrypted_content: encryptedContent,
|
|
168
|
+
}),
|
|
169
|
+
});
|
|
170
|
+
} else {
|
|
171
|
+
inputWarnings.push({
|
|
172
|
+
type: 'other',
|
|
173
|
+
message:
|
|
174
|
+
'Reasoning parts without itemId or encrypted content cannot be sent back to xAI. Skipping.',
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
case 'reasoning-file':
|
|
181
|
+
case 'custom':
|
|
127
182
|
case 'file': {
|
|
128
183
|
inputWarnings.push({
|
|
129
184
|
type: 'other',
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LanguageModelV4Usage } from '@ai-sdk/provider';
|
|
2
2
|
import { XaiResponsesUsage } from './xai-responses-api';
|
|
3
3
|
|
|
4
4
|
export function convertXaiResponsesUsage(
|
|
5
5
|
usage: XaiResponsesUsage,
|
|
6
|
-
):
|
|
6
|
+
): LanguageModelV4Usage {
|
|
7
7
|
const cacheReadTokens = usage.input_tokens_details?.cached_tokens ?? 0;
|
|
8
8
|
const reasoningTokens = usage.output_tokens_details?.reasoning_tokens ?? 0;
|
|
9
9
|
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LanguageModelV4FinishReason } from '@ai-sdk/provider';
|
|
2
2
|
|
|
3
3
|
export function mapXaiResponsesFinishReason(
|
|
4
4
|
finishReason: string | null | undefined,
|
|
5
|
-
):
|
|
5
|
+
): LanguageModelV4FinishReason['unified'] {
|
|
6
6
|
switch (finishReason) {
|
|
7
7
|
case 'stop':
|
|
8
8
|
case 'completed':
|
|
9
9
|
return 'stop';
|
|
10
10
|
case 'length':
|
|
11
|
+
case 'max_output_tokens':
|
|
11
12
|
return 'length';
|
|
12
13
|
case 'tool_calls':
|
|
13
14
|
case 'function_call':
|
|
@@ -26,7 +26,8 @@ export type XaiResponsesSystemMessage = {
|
|
|
26
26
|
|
|
27
27
|
export type XaiResponsesUserMessageContentPart =
|
|
28
28
|
| { type: 'input_text'; text: string }
|
|
29
|
-
| { type: 'input_image'; image_url: string }
|
|
29
|
+
| { type: 'input_image'; image_url: string }
|
|
30
|
+
| { type: 'input_file'; file_id: string };
|
|
30
31
|
|
|
31
32
|
export type XaiResponsesUserMessage = {
|
|
32
33
|
role: 'user';
|
|
@@ -223,6 +224,9 @@ const outputItemSchema = z.discriminatedUnion('type', [
|
|
|
223
224
|
type: z.literal('reasoning'),
|
|
224
225
|
id: z.string(),
|
|
225
226
|
summary: z.array(reasoningSummaryPartSchema),
|
|
227
|
+
content: z
|
|
228
|
+
.array(z.object({ type: z.string(), text: z.string() }))
|
|
229
|
+
.nullish(),
|
|
226
230
|
status: z.string(),
|
|
227
231
|
encrypted_content: z.string().nullish(),
|
|
228
232
|
}),
|
|
@@ -518,6 +522,32 @@ export const xaiResponsesChunkSchema = z.union([
|
|
|
518
522
|
output_index: z.number(),
|
|
519
523
|
output: z.string().optional(),
|
|
520
524
|
}),
|
|
525
|
+
z.object({
|
|
526
|
+
type: z.literal('response.incomplete'),
|
|
527
|
+
response: z.object({
|
|
528
|
+
incomplete_details: z.object({ reason: z.string() }).nullish(),
|
|
529
|
+
usage: xaiResponsesUsageSchema.nullish(),
|
|
530
|
+
}),
|
|
531
|
+
}),
|
|
532
|
+
z.object({
|
|
533
|
+
type: z.literal('response.failed'),
|
|
534
|
+
response: z.object({
|
|
535
|
+
error: z
|
|
536
|
+
.object({
|
|
537
|
+
code: z.string().nullish(),
|
|
538
|
+
message: z.string(),
|
|
539
|
+
})
|
|
540
|
+
.nullish(),
|
|
541
|
+
incomplete_details: z.object({ reason: z.string() }).nullish(),
|
|
542
|
+
usage: xaiResponsesUsageSchema.nullish(),
|
|
543
|
+
}),
|
|
544
|
+
}),
|
|
545
|
+
z.object({
|
|
546
|
+
type: z.literal('error'),
|
|
547
|
+
code: z.string().nullish(),
|
|
548
|
+
message: z.string(),
|
|
549
|
+
param: z.string().nullish(),
|
|
550
|
+
}),
|
|
521
551
|
z.object({
|
|
522
552
|
type: z.literal('response.done'),
|
|
523
553
|
response: xaiResponsesResponseSchema,
|