@sudobility/shapeshyft_engine 1.0.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/CLAUDE.md +27 -0
- package/README.md +11 -0
- package/dist/config/providers.d.ts +111 -0
- package/dist/config/providers.d.ts.map +1 -0
- package/dist/config/providers.js +1535 -0
- package/dist/config/providers.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api-helper.d.ts +35 -0
- package/dist/lib/api-helper.d.ts.map +1 -0
- package/dist/lib/api-helper.js +123 -0
- package/dist/lib/api-helper.js.map +1 -0
- package/dist/lib/capability-validator.d.ts +36 -0
- package/dist/lib/capability-validator.d.ts.map +1 -0
- package/dist/lib/capability-validator.js +140 -0
- package/dist/lib/capability-validator.js.map +1 -0
- package/dist/lib/media-constants.d.ts +55 -0
- package/dist/lib/media-constants.d.ts.map +1 -0
- package/dist/lib/media-constants.js +166 -0
- package/dist/lib/media-constants.js.map +1 -0
- package/dist/lib/media-conversion.d.ts +36 -0
- package/dist/lib/media-conversion.d.ts.map +1 -0
- package/dist/lib/media-conversion.js +87 -0
- package/dist/lib/media-conversion.js.map +1 -0
- package/dist/lib/media-utils.d.ts +35 -0
- package/dist/lib/media-utils.d.ts.map +1 -0
- package/dist/lib/media-utils.js +173 -0
- package/dist/lib/media-utils.js.map +1 -0
- package/dist/lib/output-limit.d.ts +38 -0
- package/dist/lib/output-limit.d.ts.map +1 -0
- package/dist/lib/output-limit.js +54 -0
- package/dist/lib/output-limit.js.map +1 -0
- package/dist/lib/prompt-builder.d.ts +94 -0
- package/dist/lib/prompt-builder.d.ts.map +1 -0
- package/dist/lib/prompt-builder.js +408 -0
- package/dist/lib/prompt-builder.js.map +1 -0
- package/dist/lib/reserved-fields.d.ts +41 -0
- package/dist/lib/reserved-fields.d.ts.map +1 -0
- package/dist/lib/reserved-fields.js +47 -0
- package/dist/lib/reserved-fields.js.map +1 -0
- package/dist/services/llm/anthropic.d.ts +16 -0
- package/dist/services/llm/anthropic.d.ts.map +1 -0
- package/dist/services/llm/anthropic.js +154 -0
- package/dist/services/llm/anthropic.js.map +1 -0
- package/dist/services/llm/custom.d.ts +51 -0
- package/dist/services/llm/custom.d.ts.map +1 -0
- package/dist/services/llm/custom.js +419 -0
- package/dist/services/llm/custom.js.map +1 -0
- package/dist/services/llm/extract-json.d.ts +17 -0
- package/dist/services/llm/extract-json.d.ts.map +1 -0
- package/dist/services/llm/extract-json.js +60 -0
- package/dist/services/llm/extract-json.js.map +1 -0
- package/dist/services/llm/finish-reason.d.ts +21 -0
- package/dist/services/llm/finish-reason.d.ts.map +1 -0
- package/dist/services/llm/finish-reason.js +55 -0
- package/dist/services/llm/finish-reason.js.map +1 -0
- package/dist/services/llm/gemini.d.ts +26 -0
- package/dist/services/llm/gemini.d.ts.map +1 -0
- package/dist/services/llm/gemini.js +178 -0
- package/dist/services/llm/gemini.js.map +1 -0
- package/dist/services/llm/groq.d.ts +28 -0
- package/dist/services/llm/groq.d.ts.map +1 -0
- package/dist/services/llm/groq.js +237 -0
- package/dist/services/llm/groq.js.map +1 -0
- package/dist/services/llm/index.d.ts +19 -0
- package/dist/services/llm/index.d.ts.map +1 -0
- package/dist/services/llm/index.js +88 -0
- package/dist/services/llm/index.js.map +1 -0
- package/dist/services/llm/openai.d.ts +101 -0
- package/dist/services/llm/openai.d.ts.map +1 -0
- package/dist/services/llm/openai.js +561 -0
- package/dist/services/llm/openai.js.map +1 -0
- package/dist/services/llm/types.d.ts +110 -0
- package/dist/services/llm/types.d.ts.map +1 -0
- package/dist/services/llm/types.js +16 -0
- package/dist/services/llm/types.js.map +1 -0
- package/dist/types/index.d.ts +812 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +366 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +93 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media constants for multimodal support.
|
|
3
|
+
* Single source of truth for MIME types, size limits, and validation.
|
|
4
|
+
*/
|
|
5
|
+
// =============================================================================
|
|
6
|
+
// MIME Type Allowlists
|
|
7
|
+
// =============================================================================
|
|
8
|
+
/** Allowed MIME types per media type (directly supported by LLMs) */
|
|
9
|
+
export const ALLOWED_MIME_TYPES = {
|
|
10
|
+
image: ["image/jpeg", "image/png", "image/gif", "image/webp"],
|
|
11
|
+
audio: [
|
|
12
|
+
"audio/mp3",
|
|
13
|
+
"audio/mpeg",
|
|
14
|
+
"audio/wav",
|
|
15
|
+
"audio/ogg",
|
|
16
|
+
"audio/flac",
|
|
17
|
+
"audio/m4a",
|
|
18
|
+
],
|
|
19
|
+
video: ["video/mp4", "video/webm", "video/quicktime"],
|
|
20
|
+
};
|
|
21
|
+
/** MIME types that can be converted to supported formats */
|
|
22
|
+
export const CONVERTIBLE_MIME_TYPES = {
|
|
23
|
+
image: [
|
|
24
|
+
"image/svg+xml",
|
|
25
|
+
"image/tiff",
|
|
26
|
+
"image/bmp",
|
|
27
|
+
"image/heic",
|
|
28
|
+
"image/heif",
|
|
29
|
+
"image/avif",
|
|
30
|
+
],
|
|
31
|
+
audio: [],
|
|
32
|
+
video: [],
|
|
33
|
+
};
|
|
34
|
+
/** All accepted MIME types (allowed + convertible) */
|
|
35
|
+
export const ACCEPTED_MIME_TYPES = {
|
|
36
|
+
image: [...ALLOWED_MIME_TYPES.image, ...CONVERTIBLE_MIME_TYPES.image],
|
|
37
|
+
audio: [...ALLOWED_MIME_TYPES.audio, ...CONVERTIBLE_MIME_TYPES.audio],
|
|
38
|
+
video: [...ALLOWED_MIME_TYPES.video, ...CONVERTIBLE_MIME_TYPES.video],
|
|
39
|
+
};
|
|
40
|
+
// =============================================================================
|
|
41
|
+
// Provider-Specific Audio Formats (Single Source of Truth)
|
|
42
|
+
// =============================================================================
|
|
43
|
+
/** OpenAI only supports mp3/wav for audio input */
|
|
44
|
+
export const OPENAI_AUDIO_INPUT_MIMES = [
|
|
45
|
+
"audio/mp3",
|
|
46
|
+
"audio/mpeg",
|
|
47
|
+
"audio/wav",
|
|
48
|
+
];
|
|
49
|
+
/** Gemini supports more audio formats */
|
|
50
|
+
export const GEMINI_AUDIO_INPUT_MIMES = [
|
|
51
|
+
"audio/mp3",
|
|
52
|
+
"audio/mpeg",
|
|
53
|
+
"audio/wav",
|
|
54
|
+
"audio/ogg",
|
|
55
|
+
"audio/flac",
|
|
56
|
+
];
|
|
57
|
+
/** Map MIME types to OpenAI format strings */
|
|
58
|
+
export const OPENAI_MIME_TO_FORMAT = {
|
|
59
|
+
"audio/mp3": "mp3",
|
|
60
|
+
"audio/mpeg": "mp3", // mpeg is mp3
|
|
61
|
+
"audio/wav": "wav",
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Get provider-specific supported audio formats
|
|
65
|
+
*/
|
|
66
|
+
export function getProviderAudioFormats(provider) {
|
|
67
|
+
switch (provider) {
|
|
68
|
+
case "openai":
|
|
69
|
+
return OPENAI_AUDIO_INPUT_MIMES;
|
|
70
|
+
case "gemini":
|
|
71
|
+
return GEMINI_AUDIO_INPUT_MIMES;
|
|
72
|
+
default:
|
|
73
|
+
// Other providers: allow all formats in global allowlist
|
|
74
|
+
return ALLOWED_MIME_TYPES.audio;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Convert MIME type to OpenAI format string.
|
|
79
|
+
* Throws if the format is not supported by OpenAI.
|
|
80
|
+
*/
|
|
81
|
+
export function getOpenAIAudioFormat(mimeType) {
|
|
82
|
+
const format = OPENAI_MIME_TO_FORMAT[mimeType];
|
|
83
|
+
if (!format) {
|
|
84
|
+
throw new Error(`OpenAI does not support audio format: ${mimeType}. ` +
|
|
85
|
+
`Supported: ${OPENAI_AUDIO_INPUT_MIMES.join(", ")}`);
|
|
86
|
+
}
|
|
87
|
+
return format;
|
|
88
|
+
}
|
|
89
|
+
// =============================================================================
|
|
90
|
+
// Size Limits
|
|
91
|
+
// =============================================================================
|
|
92
|
+
/** Size limits in bytes per media type */
|
|
93
|
+
export const SIZE_LIMITS = {
|
|
94
|
+
image: 20 * 1024 * 1024, // 20 MB
|
|
95
|
+
audio: 25 * 1024 * 1024, // 25 MB
|
|
96
|
+
video: 10 * 1024 * 1024, // 10 MB (base64 only, URLs handled by provider)
|
|
97
|
+
};
|
|
98
|
+
// =============================================================================
|
|
99
|
+
// Regex Patterns
|
|
100
|
+
// =============================================================================
|
|
101
|
+
/**
|
|
102
|
+
* Data URL pattern for base64-encoded media.
|
|
103
|
+
* Captures: [1] full MIME type, [2] media category, [3] base64 data
|
|
104
|
+
*/
|
|
105
|
+
export const DATA_URL_REGEX = /^data:((image|audio|video)\/[\w+-]+);base64,(.+)$/;
|
|
106
|
+
/**
|
|
107
|
+
* Provider-native URL pattern (gs:// for Gemini).
|
|
108
|
+
* SECURITY: Only allow gs:// URLs to prevent SSRF.
|
|
109
|
+
*/
|
|
110
|
+
export const PROVIDER_URL_REGEX = /^gs:\/\/[\w-]+\/.+$/;
|
|
111
|
+
// =============================================================================
|
|
112
|
+
// Validation Helpers
|
|
113
|
+
// =============================================================================
|
|
114
|
+
/**
|
|
115
|
+
* Check if a MIME type is allowed for a given media type
|
|
116
|
+
*/
|
|
117
|
+
export function isAllowedMimeType(type, mimeType) {
|
|
118
|
+
return ALLOWED_MIME_TYPES[type]?.includes(mimeType) ?? false;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get the size limit for a media type
|
|
122
|
+
*/
|
|
123
|
+
export function getSizeLimit(type) {
|
|
124
|
+
return SIZE_LIMITS[type] ?? 0;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Infer media type from MIME type string
|
|
128
|
+
*/
|
|
129
|
+
export function inferMediaTypeFromMime(mimeType) {
|
|
130
|
+
if (mimeType.startsWith("image/"))
|
|
131
|
+
return "image";
|
|
132
|
+
if (mimeType.startsWith("audio/"))
|
|
133
|
+
return "audio";
|
|
134
|
+
if (mimeType.startsWith("video/"))
|
|
135
|
+
return "video";
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Infer MIME type from file extension in URL
|
|
140
|
+
*/
|
|
141
|
+
export function inferMimeFromUrl(url) {
|
|
142
|
+
const extensionMatch = url.match(/\.(\w+)(?:\?|$)/);
|
|
143
|
+
if (!extensionMatch)
|
|
144
|
+
return undefined;
|
|
145
|
+
const ext = extensionMatch[1].toLowerCase();
|
|
146
|
+
const mimeMap = {
|
|
147
|
+
// Images
|
|
148
|
+
jpg: "image/jpeg",
|
|
149
|
+
jpeg: "image/jpeg",
|
|
150
|
+
png: "image/png",
|
|
151
|
+
gif: "image/gif",
|
|
152
|
+
webp: "image/webp",
|
|
153
|
+
// Audio
|
|
154
|
+
mp3: "audio/mp3",
|
|
155
|
+
wav: "audio/wav",
|
|
156
|
+
ogg: "audio/ogg",
|
|
157
|
+
flac: "audio/flac",
|
|
158
|
+
m4a: "audio/m4a",
|
|
159
|
+
// Video
|
|
160
|
+
mp4: "video/mp4",
|
|
161
|
+
webm: "video/webm",
|
|
162
|
+
mov: "video/quicktime",
|
|
163
|
+
};
|
|
164
|
+
return mimeMap[ext];
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=media-constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-constants.js","sourceRoot":"","sources":["../../src/lib/media-constants.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media conversion utilities for converting unsupported image formats to supported ones.
|
|
3
|
+
* Used to convert SVG and other formats to PNG before sending to LLM.
|
|
4
|
+
*/
|
|
5
|
+
import type { MediaContent } from "../types/index.js";
|
|
6
|
+
/**
|
|
7
|
+
* Check if a MIME type needs conversion
|
|
8
|
+
*/
|
|
9
|
+
export declare function needsConversion(mimeType: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Check if a MIME type is convertible (but not currently supported)
|
|
12
|
+
*/
|
|
13
|
+
export declare function isConvertibleMimeType(mimeType: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Convert an image buffer to PNG using sharp
|
|
16
|
+
*/
|
|
17
|
+
export declare function convertToPng(inputBuffer: Buffer, mimeType: string): Promise<Buffer>;
|
|
18
|
+
/**
|
|
19
|
+
* Convert a base64-encoded image to PNG
|
|
20
|
+
* Returns the new base64 data and MIME type
|
|
21
|
+
*/
|
|
22
|
+
export declare function convertBase64ToPng(base64Data: string, mimeType: string): Promise<{
|
|
23
|
+
data: string;
|
|
24
|
+
mimeType: string;
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* Convert media content if needed.
|
|
28
|
+
* Returns the original or converted media.
|
|
29
|
+
*/
|
|
30
|
+
export declare function convertMediaIfNeeded(media: MediaContent): Promise<MediaContent>;
|
|
31
|
+
/**
|
|
32
|
+
* Convert all media in an array if needed.
|
|
33
|
+
* Returns a new array with converted media.
|
|
34
|
+
*/
|
|
35
|
+
export declare function convertAllMediaIfNeeded(mediaArray: MediaContent[]): Promise<MediaContent[]>;
|
|
36
|
+
//# sourceMappingURL=media-conversion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-conversion.d.ts","sourceRoot":"","sources":["../../src/lib/media-conversion.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGtD;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAOzD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAWjB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAO7C;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,YAAY,CAAC,CAmCvB;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,YAAY,EAAE,GACzB,OAAO,CAAC,YAAY,EAAE,CAAC,CAEzB"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media conversion utilities for converting unsupported image formats to supported ones.
|
|
3
|
+
* Used to convert SVG and other formats to PNG before sending to LLM.
|
|
4
|
+
*/
|
|
5
|
+
import sharp from "sharp";
|
|
6
|
+
import { ALLOWED_MIME_TYPES, CONVERTIBLE_MIME_TYPES } from "./media-constants.js";
|
|
7
|
+
/**
|
|
8
|
+
* Check if a MIME type needs conversion
|
|
9
|
+
*/
|
|
10
|
+
export function needsConversion(mimeType) {
|
|
11
|
+
// If already supported, no conversion needed
|
|
12
|
+
if (ALLOWED_MIME_TYPES.image.includes(mimeType)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
// Check if it's a convertible format
|
|
16
|
+
return CONVERTIBLE_MIME_TYPES.image.includes(mimeType);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Check if a MIME type is convertible (but not currently supported)
|
|
20
|
+
*/
|
|
21
|
+
export function isConvertibleMimeType(mimeType) {
|
|
22
|
+
return CONVERTIBLE_MIME_TYPES.image.includes(mimeType);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Convert an image buffer to PNG using sharp
|
|
26
|
+
*/
|
|
27
|
+
export async function convertToPng(inputBuffer, mimeType) {
|
|
28
|
+
let sharpInstance = sharp(inputBuffer);
|
|
29
|
+
// For SVG, we need to handle it specially with density for better quality
|
|
30
|
+
if (mimeType === "image/svg+xml") {
|
|
31
|
+
// Re-create sharp instance with density option for SVG
|
|
32
|
+
sharpInstance = sharp(inputBuffer, { density: 300 });
|
|
33
|
+
}
|
|
34
|
+
// Convert to PNG
|
|
35
|
+
return sharpInstance.png().toBuffer();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Convert a base64-encoded image to PNG
|
|
39
|
+
* Returns the new base64 data and MIME type
|
|
40
|
+
*/
|
|
41
|
+
export async function convertBase64ToPng(base64Data, mimeType) {
|
|
42
|
+
const inputBuffer = Buffer.from(base64Data, "base64");
|
|
43
|
+
const outputBuffer = await convertToPng(inputBuffer, mimeType);
|
|
44
|
+
return {
|
|
45
|
+
data: outputBuffer.toString("base64"),
|
|
46
|
+
mimeType: "image/png",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Convert media content if needed.
|
|
51
|
+
* Returns the original or converted media.
|
|
52
|
+
*/
|
|
53
|
+
export async function convertMediaIfNeeded(media) {
|
|
54
|
+
// Only convert images
|
|
55
|
+
if (media.type !== "image") {
|
|
56
|
+
return media;
|
|
57
|
+
}
|
|
58
|
+
// Only convert if format is base64 (can't convert URLs)
|
|
59
|
+
if (media.format !== "base64") {
|
|
60
|
+
return media;
|
|
61
|
+
}
|
|
62
|
+
// Check if conversion is needed
|
|
63
|
+
if (!needsConversion(media.mimeType)) {
|
|
64
|
+
return media;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const { data, mimeType } = await convertBase64ToPng(media.data, media.mimeType);
|
|
68
|
+
return {
|
|
69
|
+
...media,
|
|
70
|
+
data,
|
|
71
|
+
mimeType,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
// If conversion fails, throw with helpful message
|
|
76
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
77
|
+
throw new Error(`Failed to convert ${media.mimeType} to PNG: ${errorMessage}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Convert all media in an array if needed.
|
|
82
|
+
* Returns a new array with converted media.
|
|
83
|
+
*/
|
|
84
|
+
export async function convertAllMediaIfNeeded(mediaArray) {
|
|
85
|
+
return Promise.all(mediaArray.map(convertMediaIfNeeded));
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=media-conversion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-conversion.js","sourceRoot":"","sources":["../../src/lib/media-conversion.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAElF;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,6CAA6C;IAC7C,IAAI,kBAAkB,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,qCAAqC;IACrC,OAAO,sBAAsB,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACzD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAgB;IACpD,OAAO,sBAAsB,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACzD,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,WAAmB,EACnB,QAAgB;IAEhB,IAAI,aAAa,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC;IAEvC,0EAA0E;IAC1E,IAAI,QAAQ,KAAK,eAAe,EAAE,CAAC;QACjC,uDAAuD;QACvD,aAAa,GAAG,KAAK,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,iBAAiB;IACjB,OAAO,aAAa,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;AACxC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,UAAkB,EAClB,QAAgB;IAEhB,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IACtD,MAAM,YAAY,GAAG,MAAM,YAAY,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IAC/D,OAAO;QACL,IAAI,EAAE,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACrC,QAAQ,EAAE,WAAW;KACtB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,KAAmB;IAEnB,sBAAsB;IACtB,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,wDAAwD;IACxD,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,gCAAgC;IAChC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,CAAC;QACH,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,MAAM,kBAAkB,CACjD,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,QAAQ,CACf,CAAC;QAEF,OAAO;YACL,GAAG,KAAK;YACR,IAAI;YACJ,QAAQ;SACT,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,kDAAkD;QAClD,MAAM,YAAY,GAChB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;QAC3D,MAAM,IAAI,KAAK,CACb,qBAAqB,KAAK,CAAC,QAAQ,YAAY,YAAY,EAAE,CAC9D,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,UAA0B;IAE1B,OAAO,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media utilities for extracting and parsing media from input data.
|
|
3
|
+
*/
|
|
4
|
+
import type { MediaContent } from "../types/index.js";
|
|
5
|
+
export interface ExtractedMedia {
|
|
6
|
+
/** Input data with media fields replaced by placeholders */
|
|
7
|
+
cleanedInput: Record<string, unknown>;
|
|
8
|
+
/** Extracted media content */
|
|
9
|
+
media: MediaContent[];
|
|
10
|
+
}
|
|
11
|
+
export interface ExtractionResult {
|
|
12
|
+
result?: ExtractedMedia;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
interface ParseResult {
|
|
16
|
+
media?: MediaContent;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parse a data URL into MediaContent.
|
|
21
|
+
* Returns error if invalid or unsupported.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseDataUrl(value: string, fieldName?: string): ParseResult;
|
|
24
|
+
/**
|
|
25
|
+
* Parse a provider URL (gs:// for Gemini).
|
|
26
|
+
* Returns error if URL is not allowed (SSRF prevention).
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseProviderUrl(value: string, mimeType?: string, fieldName?: string): ParseResult;
|
|
29
|
+
/**
|
|
30
|
+
* Extract media from input data.
|
|
31
|
+
* Returns cleaned input (with placeholders) and extracted media array.
|
|
32
|
+
*/
|
|
33
|
+
export declare function extractMediaFromInput(input: Record<string, unknown>): ExtractionResult;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=media-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-utils.d.ts","sourceRoot":"","sources":["../../src/lib/media-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAa,MAAM,mBAAmB,CAAC;AAcjE,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,8BAA8B;IAC9B,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,WAAW;IACnB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,WAAW,CAiC3E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,QAAQ,CAAC,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,WAAW,CAoCb;AAiGD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,gBAAgB,CAgBlB"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media utilities for extracting and parsing media from input data.
|
|
3
|
+
*/
|
|
4
|
+
import { DATA_URL_REGEX, PROVIDER_URL_REGEX, ACCEPTED_MIME_TYPES, SIZE_LIMITS, inferMimeFromUrl, inferMediaTypeFromMime, } from "./media-constants.js";
|
|
5
|
+
// =============================================================================
|
|
6
|
+
// Data URL Parsing
|
|
7
|
+
// =============================================================================
|
|
8
|
+
/**
|
|
9
|
+
* Parse a data URL into MediaContent.
|
|
10
|
+
* Returns error if invalid or unsupported.
|
|
11
|
+
*/
|
|
12
|
+
export function parseDataUrl(value, fieldName) {
|
|
13
|
+
const match = value.match(DATA_URL_REGEX);
|
|
14
|
+
if (!match) {
|
|
15
|
+
return {}; // Not a data URL
|
|
16
|
+
}
|
|
17
|
+
const mimeType = match[1];
|
|
18
|
+
const mediaCategory = match[2];
|
|
19
|
+
const data = match[3];
|
|
20
|
+
// Validate MIME type is in allowlist (includes convertible types)
|
|
21
|
+
if (!ACCEPTED_MIME_TYPES[mediaCategory]?.includes(mimeType)) {
|
|
22
|
+
return { error: `Unsupported MIME type: ${mimeType}` };
|
|
23
|
+
}
|
|
24
|
+
// Validate size (base64 is ~33% larger than binary)
|
|
25
|
+
const estimatedSize = (data.length * 3) / 4;
|
|
26
|
+
const limit = SIZE_LIMITS[mediaCategory];
|
|
27
|
+
if (estimatedSize > limit) {
|
|
28
|
+
return {
|
|
29
|
+
error: `${mediaCategory} exceeds ${limit / 1024 / 1024}MB limit`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
media: {
|
|
34
|
+
type: mediaCategory,
|
|
35
|
+
format: "base64",
|
|
36
|
+
mimeType,
|
|
37
|
+
data,
|
|
38
|
+
fieldName,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Parse a provider URL (gs:// for Gemini).
|
|
44
|
+
* Returns error if URL is not allowed (SSRF prevention).
|
|
45
|
+
*/
|
|
46
|
+
export function parseProviderUrl(value, mimeType, fieldName) {
|
|
47
|
+
// SECURITY: Reject http/https URLs to prevent SSRF
|
|
48
|
+
if (value.startsWith("http://") || value.startsWith("https://")) {
|
|
49
|
+
return {
|
|
50
|
+
error: "HTTP/HTTPS URLs not allowed. Use base64 or gs:// (Gemini only)",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// Only allow gs:// URLs for Gemini
|
|
54
|
+
if (!PROVIDER_URL_REGEX.test(value)) {
|
|
55
|
+
return {}; // Not a URL we handle
|
|
56
|
+
}
|
|
57
|
+
// Infer MIME type from URL if not provided
|
|
58
|
+
const inferredMime = mimeType ?? inferMimeFromUrl(value);
|
|
59
|
+
if (!inferredMime) {
|
|
60
|
+
return {
|
|
61
|
+
error: "Cannot infer MIME type from URL, please specify contentMediaType in schema",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const mediaType = inferMediaTypeFromMime(inferredMime);
|
|
65
|
+
if (!mediaType) {
|
|
66
|
+
return { error: `Unknown media type for MIME: ${inferredMime}` };
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
media: {
|
|
70
|
+
type: mediaType,
|
|
71
|
+
format: "url",
|
|
72
|
+
mimeType: inferredMime,
|
|
73
|
+
data: value,
|
|
74
|
+
fieldName,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
// =============================================================================
|
|
79
|
+
// Media Extraction
|
|
80
|
+
// =============================================================================
|
|
81
|
+
/**
|
|
82
|
+
* Check if a value looks like media (data URL or provider URL).
|
|
83
|
+
*/
|
|
84
|
+
function isMediaValue(value) {
|
|
85
|
+
if (typeof value !== "string")
|
|
86
|
+
return false;
|
|
87
|
+
return DATA_URL_REGEX.test(value) || PROVIDER_URL_REGEX.test(value);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Recursively extract media from input data.
|
|
91
|
+
* Replaces media fields with placeholders like "[Image: fieldName]".
|
|
92
|
+
*/
|
|
93
|
+
function extractFromObject(data, media, errors, path = "") {
|
|
94
|
+
const result = {};
|
|
95
|
+
for (const [key, value] of Object.entries(data)) {
|
|
96
|
+
const fieldPath = path ? `${path}.${key}` : key;
|
|
97
|
+
if (isMediaValue(value)) {
|
|
98
|
+
// Try to parse as data URL first
|
|
99
|
+
let parseResult = parseDataUrl(value, fieldPath);
|
|
100
|
+
// If not a data URL, try provider URL
|
|
101
|
+
if (!parseResult.media && !parseResult.error) {
|
|
102
|
+
parseResult = parseProviderUrl(value, undefined, fieldPath);
|
|
103
|
+
}
|
|
104
|
+
if (parseResult.error) {
|
|
105
|
+
errors.push(`${fieldPath}: ${parseResult.error}`);
|
|
106
|
+
result[key] = value; // Keep original on error
|
|
107
|
+
}
|
|
108
|
+
else if (parseResult.media) {
|
|
109
|
+
media.push(parseResult.media);
|
|
110
|
+
// Replace with placeholder
|
|
111
|
+
const typeLabel = parseResult.media.type.charAt(0).toUpperCase() +
|
|
112
|
+
parseResult.media.type.slice(1);
|
|
113
|
+
result[key] = `[${typeLabel}: ${key}]`;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
result[key] = value;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (Array.isArray(value)) {
|
|
120
|
+
// Recursively handle arrays
|
|
121
|
+
result[key] = value.map((item, index) => {
|
|
122
|
+
if (typeof item === "object" && item !== null) {
|
|
123
|
+
return extractFromObject(item, media, errors, `${fieldPath}[${index}]`);
|
|
124
|
+
}
|
|
125
|
+
if (isMediaValue(item)) {
|
|
126
|
+
const itemPath = `${fieldPath}[${index}]`;
|
|
127
|
+
let parseResult = parseDataUrl(item, itemPath);
|
|
128
|
+
if (!parseResult.media && !parseResult.error) {
|
|
129
|
+
parseResult = parseProviderUrl(item, undefined, itemPath);
|
|
130
|
+
}
|
|
131
|
+
if (parseResult.error) {
|
|
132
|
+
errors.push(`${itemPath}: ${parseResult.error}`);
|
|
133
|
+
return item;
|
|
134
|
+
}
|
|
135
|
+
else if (parseResult.media) {
|
|
136
|
+
media.push(parseResult.media);
|
|
137
|
+
const typeLabel = parseResult.media.type.charAt(0).toUpperCase() +
|
|
138
|
+
parseResult.media.type.slice(1);
|
|
139
|
+
return `[${typeLabel}: ${itemPath}]`;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return item;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
else if (typeof value === "object" && value !== null) {
|
|
146
|
+
// Recursively handle nested objects
|
|
147
|
+
result[key] = extractFromObject(value, media, errors, fieldPath);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
result[key] = value;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Extract media from input data.
|
|
157
|
+
* Returns cleaned input (with placeholders) and extracted media array.
|
|
158
|
+
*/
|
|
159
|
+
export function extractMediaFromInput(input) {
|
|
160
|
+
const media = [];
|
|
161
|
+
const errors = [];
|
|
162
|
+
const cleanedInput = extractFromObject(input, media, errors);
|
|
163
|
+
if (errors.length > 0) {
|
|
164
|
+
return { error: errors.join("; ") };
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
result: {
|
|
168
|
+
cleanedInput,
|
|
169
|
+
media,
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=media-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-utils.js","sourceRoot":"","sources":["../../src/lib/media-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Per-invocation output ceiling resolution
|
|
3
|
+
* @description Combines an endpoint's configured `max_output_tokens` with an
|
|
4
|
+
* optional per-call override into the single ceiling handed to the provider.
|
|
5
|
+
*
|
|
6
|
+
* Its own module, free of Hono and the database, so the clamping rule -- the
|
|
7
|
+
* part with security consequences -- is testable in isolation.
|
|
8
|
+
*/
|
|
9
|
+
/** Result of resolving a ceiling: a usable value, or a caller-facing error. */
|
|
10
|
+
export type ResolvedMaxOutputTokens = {
|
|
11
|
+
ok: true;
|
|
12
|
+
value: number | null;
|
|
13
|
+
} | {
|
|
14
|
+
ok: false;
|
|
15
|
+
error: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the output ceiling for one invocation.
|
|
19
|
+
*
|
|
20
|
+
* The per-call value is a **ceiling, not a raise**: it is clamped to the
|
|
21
|
+
* endpoint's own ceiling, so a caller can only ever ask for fewer tokens. That
|
|
22
|
+
* keeps it from becoming a way around an operator's limit.
|
|
23
|
+
*
|
|
24
|
+
* An endpoint ceiling of `null` means no protection at all. A per-call value is
|
|
25
|
+
* still honoured in that case -- asking for less than unlimited is safe, and it
|
|
26
|
+
* is the only protection available on endpoints that predate this field.
|
|
27
|
+
*
|
|
28
|
+
* A malformed per-call value is an error rather than a silent fallback: a caller
|
|
29
|
+
* who typos their ceiling should learn immediately, not discover later that the
|
|
30
|
+
* protection they thought they had was never applied.
|
|
31
|
+
*
|
|
32
|
+
* @param endpointCeiling - The endpoint's configured ceiling, or null for none
|
|
33
|
+
* @param requested - The caller's `max_output_tokens`, if any. `null` and
|
|
34
|
+
* `undefined` both mean "not specified" and leave the endpoint ceiling intact.
|
|
35
|
+
* @returns The ceiling to send to the provider, or a validation error
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveMaxOutputTokens(endpointCeiling: number | null, requested: unknown): ResolvedMaxOutputTokens;
|
|
38
|
+
//# sourceMappingURL=output-limit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output-limit.d.ts","sourceRoot":"","sources":["../../src/lib/output-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,+EAA+E;AAC/E,MAAM,MAAM,uBAAuB,GAC/B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAClC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sBAAsB,CACpC,eAAe,EAAE,MAAM,GAAG,IAAI,EAC9B,SAAS,EAAE,OAAO,GACjB,uBAAuB,CA+BzB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Per-invocation output ceiling resolution
|
|
3
|
+
* @description Combines an endpoint's configured `max_output_tokens` with an
|
|
4
|
+
* optional per-call override into the single ceiling handed to the provider.
|
|
5
|
+
*
|
|
6
|
+
* Its own module, free of Hono and the database, so the clamping rule -- the
|
|
7
|
+
* part with security consequences -- is testable in isolation.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the output ceiling for one invocation.
|
|
11
|
+
*
|
|
12
|
+
* The per-call value is a **ceiling, not a raise**: it is clamped to the
|
|
13
|
+
* endpoint's own ceiling, so a caller can only ever ask for fewer tokens. That
|
|
14
|
+
* keeps it from becoming a way around an operator's limit.
|
|
15
|
+
*
|
|
16
|
+
* An endpoint ceiling of `null` means no protection at all. A per-call value is
|
|
17
|
+
* still honoured in that case -- asking for less than unlimited is safe, and it
|
|
18
|
+
* is the only protection available on endpoints that predate this field.
|
|
19
|
+
*
|
|
20
|
+
* A malformed per-call value is an error rather than a silent fallback: a caller
|
|
21
|
+
* who typos their ceiling should learn immediately, not discover later that the
|
|
22
|
+
* protection they thought they had was never applied.
|
|
23
|
+
*
|
|
24
|
+
* @param endpointCeiling - The endpoint's configured ceiling, or null for none
|
|
25
|
+
* @param requested - The caller's `max_output_tokens`, if any. `null` and
|
|
26
|
+
* `undefined` both mean "not specified" and leave the endpoint ceiling intact.
|
|
27
|
+
* @returns The ceiling to send to the provider, or a validation error
|
|
28
|
+
*/
|
|
29
|
+
export function resolveMaxOutputTokens(endpointCeiling, requested) {
|
|
30
|
+
if (requested === undefined || requested === null) {
|
|
31
|
+
return { ok: true, value: endpointCeiling };
|
|
32
|
+
}
|
|
33
|
+
// GET invocations parse their input from the query string, so a perfectly
|
|
34
|
+
// valid ceiling arrives as the string "2000". Accept that spelling, but only
|
|
35
|
+
// when it denotes a whole number exactly -- "12.5" and "2000abc" are errors.
|
|
36
|
+
const candidate = typeof requested === "string" && /^\d+$/.test(requested.trim())
|
|
37
|
+
? Number(requested.trim())
|
|
38
|
+
: requested;
|
|
39
|
+
if (typeof candidate !== "number" ||
|
|
40
|
+
!Number.isInteger(candidate) ||
|
|
41
|
+
candidate <= 0) {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
error: "max_output_tokens must be a positive integer, or omitted to use the endpoint's own limit",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// Clamp rather than reject: a caller asking for more than the operator allows
|
|
48
|
+
// gets the operator's limit, not a failed request.
|
|
49
|
+
if (endpointCeiling === null) {
|
|
50
|
+
return { ok: true, value: candidate };
|
|
51
|
+
}
|
|
52
|
+
return { ok: true, value: Math.min(candidate, endpointCeiling) };
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=output-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output-limit.js","sourceRoot":"","sources":["../../src/lib/output-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,sBAAsB,CACpC,eAA8B,EAC9B,SAAkB;IAElB,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QAClD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;IAC9C,CAAC;IAED,0EAA0E;IAC1E,6EAA6E;IAC7E,6EAA6E;IAC7E,MAAM,SAAS,GACb,OAAO,SAAS,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;QAC7D,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;QAC1B,CAAC,CAAC,SAAS,CAAC;IAEhB,IACE,OAAO,SAAS,KAAK,QAAQ;QAC7B,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC;QAC5B,SAAS,IAAI,CAAC,EACd,CAAC;QACD,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EACH,0FAA0F;SAC7F,CAAC;IACJ,CAAC;IAED,8EAA8E;IAC9E,mDAAmD;IACnD,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;QAC7B,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IACxC,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,eAAe,CAAC,EAAE,CAAC;AACnE,CAAC"}
|