@modelprofile.com/flexharness-models 10.5.0 → 10.7.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/dist_ts_models/index.d.ts +1 -0
- package/dist_ts_models/index.js +2 -1
- package/dist_ts_models/smartai.textfiles.d.ts +22 -0
- package/dist_ts_models/smartai.textfiles.js +133 -0
- package/package.json +1 -1
- package/readme.md +8 -0
- package/ts_models/index.ts +1 -0
- package/ts_models/readme.md +8 -0
- package/ts_models/smartai.textfiles.ts +141 -0
|
@@ -3,5 +3,6 @@ export * from './classes.modelregistry.js';
|
|
|
3
3
|
export * from './smartai.cache.js';
|
|
4
4
|
export * from './smartai.limits.js';
|
|
5
5
|
export * from './smartai.failures.js';
|
|
6
|
+
export * from './smartai.textfiles.js';
|
|
6
7
|
export { generateText, streamText, tool, jsonSchema } from './plugins.js';
|
|
7
8
|
export type { ModelMessage, ToolSet, StreamTextResult } from './plugins.js';
|
package/dist_ts_models/index.js
CHANGED
|
@@ -3,5 +3,6 @@ export * from './classes.modelregistry.js';
|
|
|
3
3
|
export * from './smartai.cache.js';
|
|
4
4
|
export * from './smartai.limits.js';
|
|
5
5
|
export * from './smartai.failures.js';
|
|
6
|
+
export * from './smartai.textfiles.js';
|
|
6
7
|
export { generateText, streamText, tool, jsonSchema } from './plugins.js';
|
|
7
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
8
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c19tb2RlbHMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyxpQkFBaUIsQ0FBQztBQUNoQyxjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLHVCQUF1QixDQUFDO0FBQ3RDLGNBQWMsd0JBQXdCLENBQUM7QUFDdkMsT0FBTyxFQUFFLFlBQVksRUFBRSxVQUFVLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxNQUFNLGNBQWMsQ0FBQyJ9
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type * as plugins from './plugins.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether a media type names text: any `text/*` type, a JSON, XML, YAML, CSV, script or similar
|
|
4
|
+
* `application/*` type, a structured-syntax `+json`, `+xml` or `+yaml` type, or any type that
|
|
5
|
+
* states its `charset`. `text` alone, the top-level form a prompt may use, counts as text.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isTextMediaType(mediaType: string): boolean;
|
|
8
|
+
/** The text a model reads for one text file: its name and media type, then its content, fenced. */
|
|
9
|
+
export declare function formatTextFileForModel(file: {
|
|
10
|
+
filename?: string;
|
|
11
|
+
mediaType: string;
|
|
12
|
+
text: string;
|
|
13
|
+
}): string;
|
|
14
|
+
/**
|
|
15
|
+
* Sends the text files of user messages as text parts, for providers that read only images and
|
|
16
|
+
* PDFs as files. A file part whose data is inline text, or whose media type names text (see
|
|
17
|
+
* `isTextMediaType`), becomes one text part that carries the file name, the media type and the
|
|
18
|
+
* content decoded in the stated charset, UTF-8 by default. Images, PDFs, other binary files, URLs
|
|
19
|
+
* and provider file references are passed on unchanged. A file that names text but does not decode
|
|
20
|
+
* fails the call before the request is sent.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createTextFilePartMiddleware(): plugins.LanguageModelV4Middleware;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
const textualApplicationTypes = new Set([
|
|
2
|
+
'application/csv',
|
|
3
|
+
'application/ecmascript',
|
|
4
|
+
'application/graphql',
|
|
5
|
+
'application/javascript',
|
|
6
|
+
'application/json',
|
|
7
|
+
'application/jsonl',
|
|
8
|
+
'application/ndjson',
|
|
9
|
+
'application/sql',
|
|
10
|
+
'application/toml',
|
|
11
|
+
'application/typescript',
|
|
12
|
+
'application/x-javascript',
|
|
13
|
+
'application/x-ndjson',
|
|
14
|
+
'application/x-sh',
|
|
15
|
+
'application/x-toml',
|
|
16
|
+
'application/x-typescript',
|
|
17
|
+
'application/x-yaml',
|
|
18
|
+
'application/xml',
|
|
19
|
+
'application/yaml',
|
|
20
|
+
]);
|
|
21
|
+
const textualSuffixes = ['+json', '+xml', '+yaml'];
|
|
22
|
+
function parseMediaType(mediaType) {
|
|
23
|
+
const [essence = '', ...parameters] = mediaType.split(';');
|
|
24
|
+
let charset;
|
|
25
|
+
for (const parameter of parameters) {
|
|
26
|
+
const separator = parameter.indexOf('=');
|
|
27
|
+
if (separator === -1)
|
|
28
|
+
continue;
|
|
29
|
+
if (parameter.slice(0, separator).trim().toLowerCase() !== 'charset')
|
|
30
|
+
continue;
|
|
31
|
+
charset = parameter.slice(separator + 1).trim().replace(/^"(.*)"$/u, '$1');
|
|
32
|
+
}
|
|
33
|
+
return { essence: essence.trim().toLowerCase(), ...(charset ? { charset } : {}) };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Whether a media type names text: any `text/*` type, a JSON, XML, YAML, CSV, script or similar
|
|
37
|
+
* `application/*` type, a structured-syntax `+json`, `+xml` or `+yaml` type, or any type that
|
|
38
|
+
* states its `charset`. `text` alone, the top-level form a prompt may use, counts as text.
|
|
39
|
+
*/
|
|
40
|
+
export function isTextMediaType(mediaType) {
|
|
41
|
+
const { essence, charset } = parseMediaType(mediaType);
|
|
42
|
+
if (charset !== undefined)
|
|
43
|
+
return true;
|
|
44
|
+
if (essence === 'text' || essence.startsWith('text/'))
|
|
45
|
+
return true;
|
|
46
|
+
if (textualApplicationTypes.has(essence))
|
|
47
|
+
return true;
|
|
48
|
+
return essence.startsWith('application/') && textualSuffixes.some((suffix) => essence.endsWith(suffix));
|
|
49
|
+
}
|
|
50
|
+
function decodeBase64(data) {
|
|
51
|
+
const binary = atob(data);
|
|
52
|
+
const bytes = new Uint8Array(binary.length);
|
|
53
|
+
for (let index = 0; index < binary.length; index++)
|
|
54
|
+
bytes[index] = binary.charCodeAt(index);
|
|
55
|
+
return bytes;
|
|
56
|
+
}
|
|
57
|
+
function describeFile(part) {
|
|
58
|
+
return part.filename === undefined ? `file part of media type ${part.mediaType}` : `file "${part.filename}"`;
|
|
59
|
+
}
|
|
60
|
+
function decodeText(part, data) {
|
|
61
|
+
const charset = parseMediaType(part.mediaType).charset ?? 'utf-8';
|
|
62
|
+
let decoder;
|
|
63
|
+
try {
|
|
64
|
+
decoder = new TextDecoder(charset, { fatal: true });
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
throw new Error(`The ${describeFile(part)} names charset "${charset}", which cannot be decoded.`, { cause: error });
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
return decoder.decode(typeof data === 'string' ? decodeBase64(data) : data);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
throw new Error(`The ${describeFile(part)} is not valid ${charset} text.`, { cause: error });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** The text a model reads for one text file: its name and media type, then its content, fenced. */
|
|
77
|
+
export function formatTextFileForModel(file) {
|
|
78
|
+
const longestBacktickRun = Math.max(0, ...Array.from(file.text.matchAll(/`+/gu), (match) => match[0].length));
|
|
79
|
+
const fence = '`'.repeat(Math.max(3, longestBacktickRun + 1));
|
|
80
|
+
const heading = file.filename === undefined
|
|
81
|
+
? `Attached file (${file.mediaType}):`
|
|
82
|
+
: `Attached file ${file.filename} (${file.mediaType}):`;
|
|
83
|
+
const body = file.text.endsWith('\n') ? file.text : `${file.text}\n`;
|
|
84
|
+
return `${heading}\n${fence}\n${body}${fence}`;
|
|
85
|
+
}
|
|
86
|
+
function inlineTextFile(part) {
|
|
87
|
+
if (part.type !== 'file')
|
|
88
|
+
return part;
|
|
89
|
+
let text;
|
|
90
|
+
if (part.data.type === 'text') {
|
|
91
|
+
text = part.data.text;
|
|
92
|
+
}
|
|
93
|
+
else if (part.data.type === 'data' && isTextMediaType(part.mediaType)) {
|
|
94
|
+
text = decodeText(part, part.data.data);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
return part;
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
type: 'text',
|
|
101
|
+
text: formatTextFileForModel({
|
|
102
|
+
...(part.filename === undefined ? {} : { filename: part.filename }),
|
|
103
|
+
mediaType: part.mediaType,
|
|
104
|
+
text,
|
|
105
|
+
}),
|
|
106
|
+
...(part.providerOptions === undefined ? {} : { providerOptions: part.providerOptions }),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Sends the text files of user messages as text parts, for providers that read only images and
|
|
111
|
+
* PDFs as files. A file part whose data is inline text, or whose media type names text (see
|
|
112
|
+
* `isTextMediaType`), becomes one text part that carries the file name, the media type and the
|
|
113
|
+
* content decoded in the stated charset, UTF-8 by default. Images, PDFs, other binary files, URLs
|
|
114
|
+
* and provider file references are passed on unchanged. A file that names text but does not decode
|
|
115
|
+
* fails the call before the request is sent.
|
|
116
|
+
*/
|
|
117
|
+
export function createTextFilePartMiddleware() {
|
|
118
|
+
return {
|
|
119
|
+
specificationVersion: 'v4',
|
|
120
|
+
transformParams: async ({ params }) => {
|
|
121
|
+
const hasTextFile = params.prompt.some((message) => message.role === 'user' && message.content.some((part) => part.type === 'file' && (part.data.type === 'text' || (part.data.type === 'data' && isTextMediaType(part.mediaType)))));
|
|
122
|
+
if (!hasTextFile)
|
|
123
|
+
return params;
|
|
124
|
+
return {
|
|
125
|
+
...params,
|
|
126
|
+
prompt: params.prompt.map((message) => message.role === 'user'
|
|
127
|
+
? { ...message, content: message.content.map(inlineTextFile) }
|
|
128
|
+
: message),
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRhaS50ZXh0ZmlsZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c19tb2RlbHMvc21hcnRhaS50ZXh0ZmlsZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBV0EsTUFBTSx1QkFBdUIsR0FBRyxJQUFJLEdBQUcsQ0FBQztJQUN0QyxpQkFBaUI7SUFDakIsd0JBQXdCO0lBQ3hCLHFCQUFxQjtJQUNyQix3QkFBd0I7SUFDeEIsa0JBQWtCO0lBQ2xCLG1CQUFtQjtJQUNuQixvQkFBb0I7SUFDcEIsaUJBQWlCO0lBQ2pCLGtCQUFrQjtJQUNsQix3QkFBd0I7SUFDeEIsMEJBQTBCO0lBQzFCLHNCQUFzQjtJQUN0QixrQkFBa0I7SUFDbEIsb0JBQW9CO0lBQ3BCLDBCQUEwQjtJQUMxQixvQkFBb0I7SUFDcEIsaUJBQWlCO0lBQ2pCLGtCQUFrQjtDQUNuQixDQUFDLENBQUM7QUFFSCxNQUFNLGVBQWUsR0FBRyxDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFFbkQsU0FBUyxjQUFjLENBQUMsU0FBaUI7SUFDdkMsTUFBTSxDQUFDLE9BQU8sR0FBRyxFQUFFLEVBQUUsR0FBRyxVQUFVLENBQUMsR0FBRyxTQUFTLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQzNELElBQUksT0FBMkIsQ0FBQztJQUNoQyxLQUFLLE1BQU0sU0FBUyxJQUFJLFVBQVUsRUFBRSxDQUFDO1FBQ25DLE1BQU0sU0FBUyxHQUFHLFNBQVMsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDekMsSUFBSSxTQUFTLEtBQUssQ0FBQyxDQUFDO1lBQUUsU0FBUztRQUMvQixJQUFJLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLFNBQVMsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRSxLQUFLLFNBQVM7WUFBRSxTQUFTO1FBQy9FLE9BQU8sR0FBRyxTQUFTLENBQUMsS0FBSyxDQUFDLFNBQVMsR0FB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
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"author": "Task Venture Capital GmbH",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"name": "@modelprofile.com/flexharness-models",
|
|
5
|
-
"version": "10.
|
|
5
|
+
"version": "10.7.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"description": "Provider-independent model contracts, prompt caching and explicit model registration.",
|
|
8
8
|
"main": "./dist_ts_models/index.js",
|
package/readme.md
CHANGED
|
@@ -40,6 +40,14 @@ most 64 characters, and never when it looks like a credential: a key or token pr
|
|
|
40
40
|
`sk_`, `pk_`, `ghp_` or `eyJ`, or a long run of letters and digits. The description follows `cause`
|
|
41
41
|
and the last attempt of a retried call.
|
|
42
42
|
|
|
43
|
+
`createTextFilePartMiddleware()` sends the text files of user messages as text parts, for
|
|
44
|
+
providers that read only images and PDFs as files. A file part with inline text, or whose media
|
|
45
|
+
type names text (`isTextMediaType`: `text/*`, JSON, XML, YAML, CSV, script types, `+json`, `+xml`
|
|
46
|
+
and `+yaml` types, and any type with a `charset`), becomes one text part with the file name, the
|
|
47
|
+
media type and the content decoded in the stated charset (UTF-8 by default), in a fence longer
|
|
48
|
+
than any backtick run in the content. Images, PDFs, other binary files, URLs and file references
|
|
49
|
+
pass unchanged; a file that names text but does not decode fails before the request is sent.
|
|
50
|
+
|
|
43
51
|
## License and Legal Information
|
|
44
52
|
|
|
45
53
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the repository license file.
|
package/ts_models/index.ts
CHANGED
|
@@ -3,5 +3,6 @@ export * from './classes.modelregistry.js';
|
|
|
3
3
|
export * from './smartai.cache.js';
|
|
4
4
|
export * from './smartai.limits.js';
|
|
5
5
|
export * from './smartai.failures.js';
|
|
6
|
+
export * from './smartai.textfiles.js';
|
|
6
7
|
export { generateText, streamText, tool, jsonSchema } from './plugins.js';
|
|
7
8
|
export type { ModelMessage, ToolSet, StreamTextResult } from './plugins.js';
|
package/ts_models/readme.md
CHANGED
|
@@ -40,6 +40,14 @@ most 64 characters, and never when it looks like a credential: a key or token pr
|
|
|
40
40
|
`sk_`, `pk_`, `ghp_` or `eyJ`, or a long run of letters and digits. The description follows `cause`
|
|
41
41
|
and the last attempt of a retried call.
|
|
42
42
|
|
|
43
|
+
`createTextFilePartMiddleware()` sends the text files of user messages as text parts, for
|
|
44
|
+
providers that read only images and PDFs as files. A file part with inline text, or whose media
|
|
45
|
+
type names text (`isTextMediaType`: `text/*`, JSON, XML, YAML, CSV, script types, `+json`, `+xml`
|
|
46
|
+
and `+yaml` types, and any type with a `charset`), becomes one text part with the file name, the
|
|
47
|
+
media type and the content decoded in the stated charset (UTF-8 by default), in a fence longer
|
|
48
|
+
than any backtick run in the content. Images, PDFs, other binary files, URLs and file references
|
|
49
|
+
pass unchanged; a file that names text but does not decode fails before the request is sent.
|
|
50
|
+
|
|
43
51
|
## License and Legal Information
|
|
44
52
|
|
|
45
53
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the repository license file.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type * as plugins from './plugins.js';
|
|
2
|
+
|
|
3
|
+
type TPromptMessage = plugins.LanguageModelV4Prompt[number];
|
|
4
|
+
type TUserContentPart = Extract<TPromptMessage, { role: 'user' }>['content'][number];
|
|
5
|
+
type TFilePart = Extract<TUserContentPart, { type: 'file' }>;
|
|
6
|
+
|
|
7
|
+
interface IParsedMediaType {
|
|
8
|
+
essence: string;
|
|
9
|
+
charset?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const textualApplicationTypes = new Set([
|
|
13
|
+
'application/csv',
|
|
14
|
+
'application/ecmascript',
|
|
15
|
+
'application/graphql',
|
|
16
|
+
'application/javascript',
|
|
17
|
+
'application/json',
|
|
18
|
+
'application/jsonl',
|
|
19
|
+
'application/ndjson',
|
|
20
|
+
'application/sql',
|
|
21
|
+
'application/toml',
|
|
22
|
+
'application/typescript',
|
|
23
|
+
'application/x-javascript',
|
|
24
|
+
'application/x-ndjson',
|
|
25
|
+
'application/x-sh',
|
|
26
|
+
'application/x-toml',
|
|
27
|
+
'application/x-typescript',
|
|
28
|
+
'application/x-yaml',
|
|
29
|
+
'application/xml',
|
|
30
|
+
'application/yaml',
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
const textualSuffixes = ['+json', '+xml', '+yaml'];
|
|
34
|
+
|
|
35
|
+
function parseMediaType(mediaType: string): IParsedMediaType {
|
|
36
|
+
const [essence = '', ...parameters] = mediaType.split(';');
|
|
37
|
+
let charset: string | undefined;
|
|
38
|
+
for (const parameter of parameters) {
|
|
39
|
+
const separator = parameter.indexOf('=');
|
|
40
|
+
if (separator === -1) continue;
|
|
41
|
+
if (parameter.slice(0, separator).trim().toLowerCase() !== 'charset') continue;
|
|
42
|
+
charset = parameter.slice(separator + 1).trim().replace(/^"(.*)"$/u, '$1');
|
|
43
|
+
}
|
|
44
|
+
return { essence: essence.trim().toLowerCase(), ...(charset ? { charset } : {}) };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Whether a media type names text: any `text/*` type, a JSON, XML, YAML, CSV, script or similar
|
|
49
|
+
* `application/*` type, a structured-syntax `+json`, `+xml` or `+yaml` type, or any type that
|
|
50
|
+
* states its `charset`. `text` alone, the top-level form a prompt may use, counts as text.
|
|
51
|
+
*/
|
|
52
|
+
export function isTextMediaType(mediaType: string): boolean {
|
|
53
|
+
const { essence, charset } = parseMediaType(mediaType);
|
|
54
|
+
if (charset !== undefined) return true;
|
|
55
|
+
if (essence === 'text' || essence.startsWith('text/')) return true;
|
|
56
|
+
if (textualApplicationTypes.has(essence)) return true;
|
|
57
|
+
return essence.startsWith('application/') && textualSuffixes.some((suffix) => essence.endsWith(suffix));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function decodeBase64(data: string): Uint8Array {
|
|
61
|
+
const binary = atob(data);
|
|
62
|
+
const bytes = new Uint8Array(binary.length);
|
|
63
|
+
for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);
|
|
64
|
+
return bytes;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function describeFile(part: TFilePart): string {
|
|
68
|
+
return part.filename === undefined ? `file part of media type ${part.mediaType}` : `file "${part.filename}"`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function decodeText(part: TFilePart, data: Uint8Array | string): string {
|
|
72
|
+
const charset = parseMediaType(part.mediaType).charset ?? 'utf-8';
|
|
73
|
+
let decoder: TextDecoder;
|
|
74
|
+
try {
|
|
75
|
+
decoder = new TextDecoder(charset, { fatal: true });
|
|
76
|
+
} catch (error) {
|
|
77
|
+
throw new Error(`The ${describeFile(part)} names charset "${charset}", which cannot be decoded.`, { cause: error });
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
return decoder.decode(typeof data === 'string' ? decodeBase64(data) : data);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
throw new Error(`The ${describeFile(part)} is not valid ${charset} text.`, { cause: error });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The text a model reads for one text file: its name and media type, then its content, fenced. */
|
|
87
|
+
export function formatTextFileForModel(file: { filename?: string; mediaType: string; text: string }): string {
|
|
88
|
+
const longestBacktickRun = Math.max(0, ...Array.from(file.text.matchAll(/`+/gu), (match) => match[0].length));
|
|
89
|
+
const fence = '`'.repeat(Math.max(3, longestBacktickRun + 1));
|
|
90
|
+
const heading = file.filename === undefined
|
|
91
|
+
? `Attached file (${file.mediaType}):`
|
|
92
|
+
: `Attached file ${file.filename} (${file.mediaType}):`;
|
|
93
|
+
const body = file.text.endsWith('\n') ? file.text : `${file.text}\n`;
|
|
94
|
+
return `${heading}\n${fence}\n${body}${fence}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function inlineTextFile(part: TUserContentPart): TUserContentPart {
|
|
98
|
+
if (part.type !== 'file') return part;
|
|
99
|
+
let text: string;
|
|
100
|
+
if (part.data.type === 'text') {
|
|
101
|
+
text = part.data.text;
|
|
102
|
+
} else if (part.data.type === 'data' && isTextMediaType(part.mediaType)) {
|
|
103
|
+
text = decodeText(part, part.data.data);
|
|
104
|
+
} else {
|
|
105
|
+
return part;
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
type: 'text',
|
|
109
|
+
text: formatTextFileForModel({
|
|
110
|
+
...(part.filename === undefined ? {} : { filename: part.filename }),
|
|
111
|
+
mediaType: part.mediaType,
|
|
112
|
+
text,
|
|
113
|
+
}),
|
|
114
|
+
...(part.providerOptions === undefined ? {} : { providerOptions: part.providerOptions }),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Sends the text files of user messages as text parts, for providers that read only images and
|
|
120
|
+
* PDFs as files. A file part whose data is inline text, or whose media type names text (see
|
|
121
|
+
* `isTextMediaType`), becomes one text part that carries the file name, the media type and the
|
|
122
|
+
* content decoded in the stated charset, UTF-8 by default. Images, PDFs, other binary files, URLs
|
|
123
|
+
* and provider file references are passed on unchanged. A file that names text but does not decode
|
|
124
|
+
* fails the call before the request is sent.
|
|
125
|
+
*/
|
|
126
|
+
export function createTextFilePartMiddleware(): plugins.LanguageModelV4Middleware {
|
|
127
|
+
return {
|
|
128
|
+
specificationVersion: 'v4',
|
|
129
|
+
transformParams: async ({ params }) => {
|
|
130
|
+
const hasTextFile = params.prompt.some((message) => message.role === 'user' && message.content.some((part) =>
|
|
131
|
+
part.type === 'file' && (part.data.type === 'text' || (part.data.type === 'data' && isTextMediaType(part.mediaType)))));
|
|
132
|
+
if (!hasTextFile) return params;
|
|
133
|
+
return {
|
|
134
|
+
...params,
|
|
135
|
+
prompt: params.prompt.map((message) => message.role === 'user'
|
|
136
|
+
? { ...message, content: message.content.map(inlineTextFile) }
|
|
137
|
+
: message),
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|