@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.
Files changed (83) hide show
  1. package/CLAUDE.md +27 -0
  2. package/README.md +11 -0
  3. package/dist/config/providers.d.ts +111 -0
  4. package/dist/config/providers.d.ts.map +1 -0
  5. package/dist/config/providers.js +1535 -0
  6. package/dist/config/providers.js.map +1 -0
  7. package/dist/index.d.ts +27 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +27 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/lib/api-helper.d.ts +35 -0
  12. package/dist/lib/api-helper.d.ts.map +1 -0
  13. package/dist/lib/api-helper.js +123 -0
  14. package/dist/lib/api-helper.js.map +1 -0
  15. package/dist/lib/capability-validator.d.ts +36 -0
  16. package/dist/lib/capability-validator.d.ts.map +1 -0
  17. package/dist/lib/capability-validator.js +140 -0
  18. package/dist/lib/capability-validator.js.map +1 -0
  19. package/dist/lib/media-constants.d.ts +55 -0
  20. package/dist/lib/media-constants.d.ts.map +1 -0
  21. package/dist/lib/media-constants.js +166 -0
  22. package/dist/lib/media-constants.js.map +1 -0
  23. package/dist/lib/media-conversion.d.ts +36 -0
  24. package/dist/lib/media-conversion.d.ts.map +1 -0
  25. package/dist/lib/media-conversion.js +87 -0
  26. package/dist/lib/media-conversion.js.map +1 -0
  27. package/dist/lib/media-utils.d.ts +35 -0
  28. package/dist/lib/media-utils.d.ts.map +1 -0
  29. package/dist/lib/media-utils.js +173 -0
  30. package/dist/lib/media-utils.js.map +1 -0
  31. package/dist/lib/output-limit.d.ts +38 -0
  32. package/dist/lib/output-limit.d.ts.map +1 -0
  33. package/dist/lib/output-limit.js +54 -0
  34. package/dist/lib/output-limit.js.map +1 -0
  35. package/dist/lib/prompt-builder.d.ts +94 -0
  36. package/dist/lib/prompt-builder.d.ts.map +1 -0
  37. package/dist/lib/prompt-builder.js +408 -0
  38. package/dist/lib/prompt-builder.js.map +1 -0
  39. package/dist/lib/reserved-fields.d.ts +41 -0
  40. package/dist/lib/reserved-fields.d.ts.map +1 -0
  41. package/dist/lib/reserved-fields.js +47 -0
  42. package/dist/lib/reserved-fields.js.map +1 -0
  43. package/dist/services/llm/anthropic.d.ts +16 -0
  44. package/dist/services/llm/anthropic.d.ts.map +1 -0
  45. package/dist/services/llm/anthropic.js +154 -0
  46. package/dist/services/llm/anthropic.js.map +1 -0
  47. package/dist/services/llm/custom.d.ts +51 -0
  48. package/dist/services/llm/custom.d.ts.map +1 -0
  49. package/dist/services/llm/custom.js +419 -0
  50. package/dist/services/llm/custom.js.map +1 -0
  51. package/dist/services/llm/extract-json.d.ts +17 -0
  52. package/dist/services/llm/extract-json.d.ts.map +1 -0
  53. package/dist/services/llm/extract-json.js +60 -0
  54. package/dist/services/llm/extract-json.js.map +1 -0
  55. package/dist/services/llm/finish-reason.d.ts +21 -0
  56. package/dist/services/llm/finish-reason.d.ts.map +1 -0
  57. package/dist/services/llm/finish-reason.js +55 -0
  58. package/dist/services/llm/finish-reason.js.map +1 -0
  59. package/dist/services/llm/gemini.d.ts +26 -0
  60. package/dist/services/llm/gemini.d.ts.map +1 -0
  61. package/dist/services/llm/gemini.js +178 -0
  62. package/dist/services/llm/gemini.js.map +1 -0
  63. package/dist/services/llm/groq.d.ts +28 -0
  64. package/dist/services/llm/groq.d.ts.map +1 -0
  65. package/dist/services/llm/groq.js +237 -0
  66. package/dist/services/llm/groq.js.map +1 -0
  67. package/dist/services/llm/index.d.ts +19 -0
  68. package/dist/services/llm/index.d.ts.map +1 -0
  69. package/dist/services/llm/index.js +88 -0
  70. package/dist/services/llm/index.js.map +1 -0
  71. package/dist/services/llm/openai.d.ts +101 -0
  72. package/dist/services/llm/openai.d.ts.map +1 -0
  73. package/dist/services/llm/openai.js +561 -0
  74. package/dist/services/llm/openai.js.map +1 -0
  75. package/dist/services/llm/types.d.ts +110 -0
  76. package/dist/services/llm/types.d.ts.map +1 -0
  77. package/dist/services/llm/types.js +16 -0
  78. package/dist/services/llm/types.js.map +1 -0
  79. package/dist/types/index.d.ts +812 -0
  80. package/dist/types/index.d.ts.map +1 -0
  81. package/dist/types/index.js +366 -0
  82. package/dist/types/index.js.map +1 -0
  83. 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"}