llm-messages 0.3.0 → 0.4.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 +11 -0
- package/README.md +9 -4
- package/dist/index.cjs +162 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -5
- package/dist/index.d.ts +58 -5
- package/dist/index.js +162 -11
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/dist/index.d.cts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
/** A supported provider. */
|
|
11
11
|
type Provider = 'openai' | 'anthropic' | 'gemini';
|
|
12
12
|
/** A stable, machine readable code describing a non fatal conversion event. */
|
|
13
|
-
type WarningCode = 'generated-id' | 'unmapped-tool-result' | 'merged-role' | 'dropped-content' | 'invalid-json-arguments' | 'system-midstream' | 'gemini-url-image';
|
|
13
|
+
type WarningCode = 'generated-id' | 'unmapped-tool-result' | 'merged-role' | 'dropped-content' | 'invalid-json-arguments' | 'system-midstream' | 'gemini-url-image' | 'gemini-url-media' | 'unsupported-modality';
|
|
14
14
|
/** A non fatal event raised during conversion. */
|
|
15
15
|
interface Warning {
|
|
16
16
|
code: WarningCode;
|
|
@@ -33,8 +33,25 @@ interface OpenAIImagePart {
|
|
|
33
33
|
detail?: 'auto' | 'low' | 'high' | 'original';
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
type
|
|
36
|
+
interface OpenAIAudioPart {
|
|
37
|
+
type: 'input_audio';
|
|
38
|
+
/** `data` is raw base64 (no data URL prefix); `format` is `wav` or `mp3`. */
|
|
39
|
+
input_audio: {
|
|
40
|
+
data: string;
|
|
41
|
+
format: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
interface OpenAIFilePart {
|
|
45
|
+
type: 'file';
|
|
46
|
+
/** `file_data` is a `data:<mediaType>;base64,<data>` data URL, or use `file_id`. */
|
|
47
|
+
file: {
|
|
48
|
+
file_data?: string;
|
|
49
|
+
file_id?: string;
|
|
50
|
+
filename?: string;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** A content part. Unknown part types are preserved verbatim. */
|
|
54
|
+
type OpenAIContentPart = OpenAITextPart | OpenAIImagePart | OpenAIAudioPart | OpenAIFilePart | {
|
|
38
55
|
type: string;
|
|
39
56
|
[key: string]: unknown;
|
|
40
57
|
};
|
|
@@ -99,7 +116,21 @@ interface AnthropicImageBlock {
|
|
|
99
116
|
url: string;
|
|
100
117
|
};
|
|
101
118
|
}
|
|
102
|
-
|
|
119
|
+
interface AnthropicDocumentBlock {
|
|
120
|
+
type: 'document';
|
|
121
|
+
source: {
|
|
122
|
+
type: 'base64';
|
|
123
|
+
media_type: string;
|
|
124
|
+
data: string;
|
|
125
|
+
} | {
|
|
126
|
+
type: 'url';
|
|
127
|
+
url: string;
|
|
128
|
+
} | {
|
|
129
|
+
type: 'file';
|
|
130
|
+
file_id: string;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
type AnthropicContentBlock = AnthropicTextBlock | AnthropicToolUseBlock | AnthropicToolResultBlock | AnthropicImageBlock | AnthropicDocumentBlock | {
|
|
103
134
|
type: string;
|
|
104
135
|
[key: string]: unknown;
|
|
105
136
|
};
|
|
@@ -228,6 +259,28 @@ declare function parseDataUrl(url: string): {
|
|
|
228
259
|
/** Reassembles a base64 data URL. The inverse of {@link parseDataUrl}. */
|
|
229
260
|
declare function toDataUrl(mediaType: string, data: string): string;
|
|
230
261
|
|
|
262
|
+
/**
|
|
263
|
+
* A provider-neutral non-image media part (audio or document). Images keep their
|
|
264
|
+
* own dedicated path in `image.ts`; everything else flows through here.
|
|
265
|
+
*/
|
|
266
|
+
type MediaModality = 'audio' | 'document';
|
|
267
|
+
type MediaSource = {
|
|
268
|
+
kind: 'base64';
|
|
269
|
+
mediaType: string;
|
|
270
|
+
data: string;
|
|
271
|
+
} | {
|
|
272
|
+
kind: 'url';
|
|
273
|
+
url: string;
|
|
274
|
+
} | {
|
|
275
|
+
kind: 'file_id';
|
|
276
|
+
id: string;
|
|
277
|
+
};
|
|
278
|
+
interface MediaPart {
|
|
279
|
+
modality: MediaModality;
|
|
280
|
+
source: MediaSource;
|
|
281
|
+
filename?: string;
|
|
282
|
+
}
|
|
283
|
+
|
|
231
284
|
/** A provider-neutral finish reason. */
|
|
232
285
|
type FinishReason = 'stop' | 'tool_calls' | 'length' | 'content_filter' | 'unknown';
|
|
233
286
|
/** Provider-neutral token usage. */
|
|
@@ -252,4 +305,4 @@ declare function normalizeResponse(body: unknown, route: {
|
|
|
252
305
|
from: Provider;
|
|
253
306
|
}, options?: ConvertOptions): NormalizedResponse;
|
|
254
307
|
|
|
255
|
-
export { type AnthropicContentBlock, type AnthropicConversation, type AnthropicImageBlock, type AnthropicMessage, type AnthropicTextBlock, type AnthropicToolResultBlock, type AnthropicToolUseBlock, type ConversationOf, type ConvertOptions, type FinishReason, type GeminiContent, type GeminiConversation, type GeminiFileDataPart, type GeminiFunctionCallPart, type GeminiFunctionResponsePart, type GeminiInlineDataPart, type GeminiPart, type GeminiTextPart, type NormalizedImage, type NormalizedResponse, type OpenAIAssistantMessage, type OpenAIContentPart, type OpenAIImagePart, type OpenAIMessage, type OpenAISystemMessage, type OpenAITextPart, type OpenAIToolCall, type OpenAIToolMessage, type OpenAIUserMessage, type Provider, type Usage, type Warning, type WarningCode, convert, fromAnthropic, fromGemini, normalizeResponse, parseDataUrl, responseFromAnthropic, responseFromGemini, responseFromOpenAI, toAnthropic, toDataUrl, toGemini };
|
|
308
|
+
export { type AnthropicContentBlock, type AnthropicConversation, type AnthropicDocumentBlock, type AnthropicImageBlock, type AnthropicMessage, type AnthropicTextBlock, type AnthropicToolResultBlock, type AnthropicToolUseBlock, type ConversationOf, type ConvertOptions, type FinishReason, type GeminiContent, type GeminiConversation, type GeminiFileDataPart, type GeminiFunctionCallPart, type GeminiFunctionResponsePart, type GeminiInlineDataPart, type GeminiPart, type GeminiTextPart, type MediaModality, type MediaPart, type MediaSource, type NormalizedImage, type NormalizedResponse, type OpenAIAssistantMessage, type OpenAIAudioPart, type OpenAIContentPart, type OpenAIFilePart, type OpenAIImagePart, type OpenAIMessage, type OpenAISystemMessage, type OpenAITextPart, type OpenAIToolCall, type OpenAIToolMessage, type OpenAIUserMessage, type Provider, type Usage, type Warning, type WarningCode, convert, fromAnthropic, fromGemini, normalizeResponse, parseDataUrl, responseFromAnthropic, responseFromGemini, responseFromOpenAI, toAnthropic, toDataUrl, toGemini };
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
/** A supported provider. */
|
|
11
11
|
type Provider = 'openai' | 'anthropic' | 'gemini';
|
|
12
12
|
/** A stable, machine readable code describing a non fatal conversion event. */
|
|
13
|
-
type WarningCode = 'generated-id' | 'unmapped-tool-result' | 'merged-role' | 'dropped-content' | 'invalid-json-arguments' | 'system-midstream' | 'gemini-url-image';
|
|
13
|
+
type WarningCode = 'generated-id' | 'unmapped-tool-result' | 'merged-role' | 'dropped-content' | 'invalid-json-arguments' | 'system-midstream' | 'gemini-url-image' | 'gemini-url-media' | 'unsupported-modality';
|
|
14
14
|
/** A non fatal event raised during conversion. */
|
|
15
15
|
interface Warning {
|
|
16
16
|
code: WarningCode;
|
|
@@ -33,8 +33,25 @@ interface OpenAIImagePart {
|
|
|
33
33
|
detail?: 'auto' | 'low' | 'high' | 'original';
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
type
|
|
36
|
+
interface OpenAIAudioPart {
|
|
37
|
+
type: 'input_audio';
|
|
38
|
+
/** `data` is raw base64 (no data URL prefix); `format` is `wav` or `mp3`. */
|
|
39
|
+
input_audio: {
|
|
40
|
+
data: string;
|
|
41
|
+
format: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
interface OpenAIFilePart {
|
|
45
|
+
type: 'file';
|
|
46
|
+
/** `file_data` is a `data:<mediaType>;base64,<data>` data URL, or use `file_id`. */
|
|
47
|
+
file: {
|
|
48
|
+
file_data?: string;
|
|
49
|
+
file_id?: string;
|
|
50
|
+
filename?: string;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** A content part. Unknown part types are preserved verbatim. */
|
|
54
|
+
type OpenAIContentPart = OpenAITextPart | OpenAIImagePart | OpenAIAudioPart | OpenAIFilePart | {
|
|
38
55
|
type: string;
|
|
39
56
|
[key: string]: unknown;
|
|
40
57
|
};
|
|
@@ -99,7 +116,21 @@ interface AnthropicImageBlock {
|
|
|
99
116
|
url: string;
|
|
100
117
|
};
|
|
101
118
|
}
|
|
102
|
-
|
|
119
|
+
interface AnthropicDocumentBlock {
|
|
120
|
+
type: 'document';
|
|
121
|
+
source: {
|
|
122
|
+
type: 'base64';
|
|
123
|
+
media_type: string;
|
|
124
|
+
data: string;
|
|
125
|
+
} | {
|
|
126
|
+
type: 'url';
|
|
127
|
+
url: string;
|
|
128
|
+
} | {
|
|
129
|
+
type: 'file';
|
|
130
|
+
file_id: string;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
type AnthropicContentBlock = AnthropicTextBlock | AnthropicToolUseBlock | AnthropicToolResultBlock | AnthropicImageBlock | AnthropicDocumentBlock | {
|
|
103
134
|
type: string;
|
|
104
135
|
[key: string]: unknown;
|
|
105
136
|
};
|
|
@@ -228,6 +259,28 @@ declare function parseDataUrl(url: string): {
|
|
|
228
259
|
/** Reassembles a base64 data URL. The inverse of {@link parseDataUrl}. */
|
|
229
260
|
declare function toDataUrl(mediaType: string, data: string): string;
|
|
230
261
|
|
|
262
|
+
/**
|
|
263
|
+
* A provider-neutral non-image media part (audio or document). Images keep their
|
|
264
|
+
* own dedicated path in `image.ts`; everything else flows through here.
|
|
265
|
+
*/
|
|
266
|
+
type MediaModality = 'audio' | 'document';
|
|
267
|
+
type MediaSource = {
|
|
268
|
+
kind: 'base64';
|
|
269
|
+
mediaType: string;
|
|
270
|
+
data: string;
|
|
271
|
+
} | {
|
|
272
|
+
kind: 'url';
|
|
273
|
+
url: string;
|
|
274
|
+
} | {
|
|
275
|
+
kind: 'file_id';
|
|
276
|
+
id: string;
|
|
277
|
+
};
|
|
278
|
+
interface MediaPart {
|
|
279
|
+
modality: MediaModality;
|
|
280
|
+
source: MediaSource;
|
|
281
|
+
filename?: string;
|
|
282
|
+
}
|
|
283
|
+
|
|
231
284
|
/** A provider-neutral finish reason. */
|
|
232
285
|
type FinishReason = 'stop' | 'tool_calls' | 'length' | 'content_filter' | 'unknown';
|
|
233
286
|
/** Provider-neutral token usage. */
|
|
@@ -252,4 +305,4 @@ declare function normalizeResponse(body: unknown, route: {
|
|
|
252
305
|
from: Provider;
|
|
253
306
|
}, options?: ConvertOptions): NormalizedResponse;
|
|
254
307
|
|
|
255
|
-
export { type AnthropicContentBlock, type AnthropicConversation, type AnthropicImageBlock, type AnthropicMessage, type AnthropicTextBlock, type AnthropicToolResultBlock, type AnthropicToolUseBlock, type ConversationOf, type ConvertOptions, type FinishReason, type GeminiContent, type GeminiConversation, type GeminiFileDataPart, type GeminiFunctionCallPart, type GeminiFunctionResponsePart, type GeminiInlineDataPart, type GeminiPart, type GeminiTextPart, type NormalizedImage, type NormalizedResponse, type OpenAIAssistantMessage, type OpenAIContentPart, type OpenAIImagePart, type OpenAIMessage, type OpenAISystemMessage, type OpenAITextPart, type OpenAIToolCall, type OpenAIToolMessage, type OpenAIUserMessage, type Provider, type Usage, type Warning, type WarningCode, convert, fromAnthropic, fromGemini, normalizeResponse, parseDataUrl, responseFromAnthropic, responseFromGemini, responseFromOpenAI, toAnthropic, toDataUrl, toGemini };
|
|
308
|
+
export { type AnthropicContentBlock, type AnthropicConversation, type AnthropicDocumentBlock, type AnthropicImageBlock, type AnthropicMessage, type AnthropicTextBlock, type AnthropicToolResultBlock, type AnthropicToolUseBlock, type ConversationOf, type ConvertOptions, type FinishReason, type GeminiContent, type GeminiConversation, type GeminiFileDataPart, type GeminiFunctionCallPart, type GeminiFunctionResponsePart, type GeminiInlineDataPart, type GeminiPart, type GeminiTextPart, type MediaModality, type MediaPart, type MediaSource, type NormalizedImage, type NormalizedResponse, type OpenAIAssistantMessage, type OpenAIAudioPart, type OpenAIContentPart, type OpenAIFilePart, type OpenAIImagePart, type OpenAIMessage, type OpenAISystemMessage, type OpenAITextPart, type OpenAIToolCall, type OpenAIToolMessage, type OpenAIUserMessage, type Provider, type Usage, type Warning, type WarningCode, convert, fromAnthropic, fromGemini, normalizeResponse, parseDataUrl, responseFromAnthropic, responseFromGemini, responseFromOpenAI, toAnthropic, toDataUrl, toGemini };
|
package/dist/index.js
CHANGED
|
@@ -92,13 +92,16 @@ function imageToAnthropic(image) {
|
|
|
92
92
|
function imageFromGemini(part) {
|
|
93
93
|
if (isRecord(part) && isRecord(part.inlineData)) {
|
|
94
94
|
const data = part.inlineData;
|
|
95
|
-
if (typeof data.mimeType === "string" && typeof data.data === "string") {
|
|
95
|
+
if (typeof data.mimeType === "string" && typeof data.data === "string" && data.mimeType.startsWith("image/")) {
|
|
96
96
|
return { kind: "base64", mediaType: data.mimeType, data: data.data };
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
if (isRecord(part) && isRecord(part.fileData)) {
|
|
100
100
|
const data = part.fileData;
|
|
101
|
-
|
|
101
|
+
const mime = typeof data.mimeType === "string" ? data.mimeType : "";
|
|
102
|
+
if (typeof data.fileUri === "string" && (mime === "" || mime.startsWith("image/"))) {
|
|
103
|
+
return { kind: "url", url: data.fileUri };
|
|
104
|
+
}
|
|
102
105
|
}
|
|
103
106
|
return null;
|
|
104
107
|
}
|
|
@@ -113,6 +116,124 @@ function imageToGemini(image, reporter) {
|
|
|
113
116
|
return { fileData: { fileUri: image.url } };
|
|
114
117
|
}
|
|
115
118
|
|
|
119
|
+
// src/media.ts
|
|
120
|
+
function modalityFromMime(mediaType) {
|
|
121
|
+
return mediaType.startsWith("audio/") ? "audio" : "document";
|
|
122
|
+
}
|
|
123
|
+
function mediaFromOpenAI(part) {
|
|
124
|
+
if (!isRecord(part)) return null;
|
|
125
|
+
if (part.type === "input_audio" && isRecord(part.input_audio)) {
|
|
126
|
+
const audio = part.input_audio;
|
|
127
|
+
if (typeof audio.data === "string") {
|
|
128
|
+
const format = typeof audio.format === "string" ? audio.format : "wav";
|
|
129
|
+
return { modality: "audio", source: { kind: "base64", mediaType: `audio/${format}`, data: audio.data } };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (part.type === "file" && isRecord(part.file)) {
|
|
133
|
+
const file = part.file;
|
|
134
|
+
const filename = typeof file.filename === "string" ? file.filename : void 0;
|
|
135
|
+
if (typeof file.file_data === "string") {
|
|
136
|
+
const parsed = parseDataUrl(file.file_data);
|
|
137
|
+
if (parsed) return { modality: "document", source: { kind: "base64", ...parsed }, filename };
|
|
138
|
+
}
|
|
139
|
+
if (typeof file.file_id === "string") {
|
|
140
|
+
return { modality: "document", source: { kind: "file_id", id: file.file_id }, filename };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
function mediaToOpenAI(media) {
|
|
146
|
+
const { modality, source } = media;
|
|
147
|
+
if (modality === "audio") {
|
|
148
|
+
if (source.kind !== "base64") return null;
|
|
149
|
+
const audio = {
|
|
150
|
+
type: "input_audio",
|
|
151
|
+
input_audio: { data: source.data, format: source.mediaType.replace(/^audio\//, "") }
|
|
152
|
+
};
|
|
153
|
+
return audio;
|
|
154
|
+
}
|
|
155
|
+
if (source.kind === "base64") {
|
|
156
|
+
const file = {
|
|
157
|
+
type: "file",
|
|
158
|
+
file: {
|
|
159
|
+
file_data: toDataUrl(source.mediaType, source.data),
|
|
160
|
+
...media.filename ? { filename: media.filename } : {}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
return file;
|
|
164
|
+
}
|
|
165
|
+
if (source.kind === "file_id") {
|
|
166
|
+
const file = {
|
|
167
|
+
type: "file",
|
|
168
|
+
file: { file_id: source.id, ...media.filename ? { filename: media.filename } : {} }
|
|
169
|
+
};
|
|
170
|
+
return file;
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
function mediaFromAnthropic(block) {
|
|
175
|
+
if (!isRecord(block) || block.type !== "document" || !isRecord(block.source)) return null;
|
|
176
|
+
const source = block.source;
|
|
177
|
+
if (source.type === "base64" && typeof source.media_type === "string" && typeof source.data === "string") {
|
|
178
|
+
return { modality: "document", source: { kind: "base64", mediaType: source.media_type, data: source.data } };
|
|
179
|
+
}
|
|
180
|
+
if (source.type === "url" && typeof source.url === "string") {
|
|
181
|
+
return { modality: "document", source: { kind: "url", url: source.url } };
|
|
182
|
+
}
|
|
183
|
+
if (source.type === "file" && typeof source.file_id === "string") {
|
|
184
|
+
return { modality: "document", source: { kind: "file_id", id: source.file_id } };
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
function mediaToAnthropic(media, reporter) {
|
|
189
|
+
if (media.modality === "audio") {
|
|
190
|
+
reporter.warn("unsupported-modality", "Anthropic has no audio input; dropped an audio part.");
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
const { source } = media;
|
|
194
|
+
if (source.kind === "base64") {
|
|
195
|
+
return { type: "document", source: { type: "base64", media_type: source.mediaType, data: source.data } };
|
|
196
|
+
}
|
|
197
|
+
if (source.kind === "url") {
|
|
198
|
+
return { type: "document", source: { type: "url", url: source.url } };
|
|
199
|
+
}
|
|
200
|
+
return { type: "document", source: { type: "file", file_id: source.id } };
|
|
201
|
+
}
|
|
202
|
+
function mediaFromGemini(part) {
|
|
203
|
+
if (isRecord(part) && isRecord(part.inlineData)) {
|
|
204
|
+
const data = part.inlineData;
|
|
205
|
+
if (typeof data.mimeType === "string" && typeof data.data === "string" && !data.mimeType.startsWith("image/")) {
|
|
206
|
+
return {
|
|
207
|
+
modality: modalityFromMime(data.mimeType),
|
|
208
|
+
source: { kind: "base64", mediaType: data.mimeType, data: data.data }
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (isRecord(part) && isRecord(part.fileData)) {
|
|
213
|
+
const data = part.fileData;
|
|
214
|
+
const mime = typeof data.mimeType === "string" ? data.mimeType : "";
|
|
215
|
+
if (typeof data.fileUri === "string" && mime !== "" && !mime.startsWith("image/")) {
|
|
216
|
+
return { modality: modalityFromMime(mime), source: { kind: "url", url: data.fileUri } };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
function mediaToGemini(media, reporter) {
|
|
222
|
+
const { source } = media;
|
|
223
|
+
if (source.kind === "base64") {
|
|
224
|
+
return { inlineData: { mimeType: source.mediaType, data: source.data } };
|
|
225
|
+
}
|
|
226
|
+
if (source.kind === "url") {
|
|
227
|
+
reporter.warn(
|
|
228
|
+
"gemini-url-media",
|
|
229
|
+
"A media URL was emitted as Gemini fileData.fileUri; Gemini may require the Files API for non-Google URIs."
|
|
230
|
+
);
|
|
231
|
+
return { fileData: { fileUri: source.url } };
|
|
232
|
+
}
|
|
233
|
+
reporter.warn("unsupported-modality", "Gemini has no file-id media reference; dropped a file_id part.");
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
116
237
|
// src/providers/openai.ts
|
|
117
238
|
function isSystem(message) {
|
|
118
239
|
return message.role === "system" || message.role === "developer";
|
|
@@ -181,6 +302,12 @@ function userContent(content, reporter) {
|
|
|
181
302
|
blocks.push(imageToAnthropic(image));
|
|
182
303
|
continue;
|
|
183
304
|
}
|
|
305
|
+
const media = mediaFromOpenAI(part);
|
|
306
|
+
if (media) {
|
|
307
|
+
const block = mediaToAnthropic(media, reporter);
|
|
308
|
+
if (block) blocks.push(block);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
184
311
|
reporter.warn("dropped-content", "Dropped an unsupported user content part.");
|
|
185
312
|
}
|
|
186
313
|
return blocks;
|
|
@@ -222,7 +349,7 @@ function asBlocks(content) {
|
|
|
222
349
|
return content ? [{ type: "text", text: content }] : [];
|
|
223
350
|
}
|
|
224
351
|
function fromAnthropic(conversation, options = {}) {
|
|
225
|
-
|
|
352
|
+
const reporter = new Reporter(options);
|
|
226
353
|
const out = [];
|
|
227
354
|
if (conversation.system) {
|
|
228
355
|
out.push({ role: "system", content: textOf(conversation.system) });
|
|
@@ -240,7 +367,7 @@ function fromAnthropic(conversation, options = {}) {
|
|
|
240
367
|
});
|
|
241
368
|
}
|
|
242
369
|
if (contentBlocks.length > 0) {
|
|
243
|
-
out.push({ role: "user", content: userContentToOpenAI(contentBlocks) });
|
|
370
|
+
out.push({ role: "user", content: userContentToOpenAI(contentBlocks, reporter) });
|
|
244
371
|
}
|
|
245
372
|
continue;
|
|
246
373
|
}
|
|
@@ -257,15 +384,24 @@ function fromAnthropic(conversation, options = {}) {
|
|
|
257
384
|
}
|
|
258
385
|
return out;
|
|
259
386
|
}
|
|
260
|
-
function userContentToOpenAI(blocks) {
|
|
261
|
-
const
|
|
262
|
-
if (!
|
|
387
|
+
function userContentToOpenAI(blocks, reporter) {
|
|
388
|
+
const hasMedia = blocks.some((block) => imageFromAnthropic(block) !== null || mediaFromAnthropic(block) !== null);
|
|
389
|
+
if (!hasMedia) return textOf(blocks);
|
|
263
390
|
const parts = [];
|
|
264
391
|
for (const block of blocks) {
|
|
265
392
|
const image = imageFromAnthropic(block);
|
|
266
393
|
if (image) {
|
|
267
394
|
parts.push(imageToOpenAI(image));
|
|
268
|
-
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
const media = mediaFromAnthropic(block);
|
|
398
|
+
if (media) {
|
|
399
|
+
const part = mediaToOpenAI(media);
|
|
400
|
+
if (part) parts.push(part);
|
|
401
|
+
else reporter.warn("dropped-content", "A document URL has no OpenAI Chat Completions equivalent; dropped.");
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (isRecord(block) && block.type === "text" && typeof block.text === "string") {
|
|
269
405
|
parts.push({ type: "text", text: block.text });
|
|
270
406
|
}
|
|
271
407
|
}
|
|
@@ -332,6 +468,12 @@ function userParts(content, reporter) {
|
|
|
332
468
|
parts.push(imageToGemini(image, reporter));
|
|
333
469
|
continue;
|
|
334
470
|
}
|
|
471
|
+
const media = mediaFromOpenAI(part);
|
|
472
|
+
if (media) {
|
|
473
|
+
const geminiPart = mediaToGemini(media, reporter);
|
|
474
|
+
if (geminiPart) parts.push(geminiPart);
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
335
477
|
reporter.warn("dropped-content", "Dropped an unsupported user content part.");
|
|
336
478
|
}
|
|
337
479
|
return parts.length > 0 ? parts : [{ text: "" }];
|
|
@@ -401,7 +543,7 @@ function fromGemini(conversation, options = {}) {
|
|
|
401
543
|
continue;
|
|
402
544
|
}
|
|
403
545
|
const contentParts = [];
|
|
404
|
-
let
|
|
546
|
+
let hasMedia = false;
|
|
405
547
|
for (const part of parts) {
|
|
406
548
|
if (isRecord(part) && isRecord(part.functionResponse)) {
|
|
407
549
|
const fr = part.functionResponse;
|
|
@@ -412,7 +554,16 @@ function fromGemini(conversation, options = {}) {
|
|
|
412
554
|
const image = imageFromGemini(part);
|
|
413
555
|
if (image) {
|
|
414
556
|
contentParts.push(imageToOpenAI(image));
|
|
415
|
-
|
|
557
|
+
hasMedia = true;
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
const media = mediaFromGemini(part);
|
|
561
|
+
if (media) {
|
|
562
|
+
const openaiPart = mediaToOpenAI(media);
|
|
563
|
+
if (openaiPart) {
|
|
564
|
+
contentParts.push(openaiPart);
|
|
565
|
+
hasMedia = true;
|
|
566
|
+
}
|
|
416
567
|
continue;
|
|
417
568
|
}
|
|
418
569
|
if (isRecord(part) && typeof part.text === "string") {
|
|
@@ -420,7 +571,7 @@ function fromGemini(conversation, options = {}) {
|
|
|
420
571
|
}
|
|
421
572
|
}
|
|
422
573
|
if (contentParts.length > 0) {
|
|
423
|
-
if (
|
|
574
|
+
if (hasMedia) {
|
|
424
575
|
out.push({ role: "user", content: contentParts });
|
|
425
576
|
} else {
|
|
426
577
|
const text = textOf(contentParts);
|