@bowenqt/qiniu-ai-sdk 0.15.0 → 0.16.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 (68) hide show
  1. package/dist/ai/agent-graph.d.ts +5 -0
  2. package/dist/ai/agent-graph.d.ts.map +1 -1
  3. package/dist/ai/agent-graph.js +21 -2
  4. package/dist/ai/agent-graph.js.map +1 -1
  5. package/dist/ai/agent-graph.mjs +21 -2
  6. package/dist/ai/create-agent.d.ts +3 -0
  7. package/dist/ai/create-agent.d.ts.map +1 -1
  8. package/dist/ai/create-agent.js +6 -5
  9. package/dist/ai/create-agent.js.map +1 -1
  10. package/dist/ai/create-agent.mjs +6 -5
  11. package/dist/ai/generate-object.d.ts.map +1 -1
  12. package/dist/ai/generate-object.js +7 -1
  13. package/dist/ai/generate-object.js.map +1 -1
  14. package/dist/ai/generate-object.mjs +7 -1
  15. package/dist/ai/generate-text.d.ts +4 -1
  16. package/dist/ai/generate-text.d.ts.map +1 -1
  17. package/dist/ai/generate-text.js +10 -2
  18. package/dist/ai/generate-text.js.map +1 -1
  19. package/dist/ai/generate-text.mjs +10 -2
  20. package/dist/ai/internal-types.d.ts +11 -0
  21. package/dist/ai/internal-types.d.ts.map +1 -1
  22. package/dist/ai/internal-types.js +15 -0
  23. package/dist/ai/internal-types.js.map +1 -1
  24. package/dist/ai/internal-types.mjs +13 -0
  25. package/dist/ai/memory/index.d.ts +147 -0
  26. package/dist/ai/memory/index.d.ts.map +1 -0
  27. package/dist/ai/memory/index.js +240 -0
  28. package/dist/ai/memory/index.js.map +1 -0
  29. package/dist/ai/memory/index.mjs +234 -0
  30. package/dist/ai/nodes/memory-node.d.ts.map +1 -1
  31. package/dist/ai/nodes/memory-node.js +14 -16
  32. package/dist/ai/nodes/memory-node.js.map +1 -1
  33. package/dist/ai/nodes/memory-node.mjs +15 -17
  34. package/dist/ai/stream-object.d.ts +109 -0
  35. package/dist/ai/stream-object.d.ts.map +1 -0
  36. package/dist/ai/stream-object.js +383 -0
  37. package/dist/ai/stream-object.js.map +1 -0
  38. package/dist/ai/stream-object.mjs +347 -0
  39. package/dist/index.d.ts +8 -2
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +16 -2
  42. package/dist/index.js.map +1 -1
  43. package/dist/index.mjs +7 -1
  44. package/dist/lib/capability-cache.d.ts +72 -0
  45. package/dist/lib/capability-cache.d.ts.map +1 -0
  46. package/dist/lib/capability-cache.js +117 -0
  47. package/dist/lib/capability-cache.js.map +1 -0
  48. package/dist/lib/capability-cache.mjs +113 -0
  49. package/dist/lib/content-converter.d.ts +33 -0
  50. package/dist/lib/content-converter.d.ts.map +1 -0
  51. package/dist/lib/content-converter.js +166 -0
  52. package/dist/lib/content-converter.js.map +1 -0
  53. package/dist/lib/content-converter.mjs +161 -0
  54. package/dist/lib/messages.js +4 -3
  55. package/dist/lib/messages.js.map +1 -1
  56. package/dist/lib/messages.mjs +4 -3
  57. package/dist/lib/partial-json-parser.d.ts +63 -0
  58. package/dist/lib/partial-json-parser.d.ts.map +1 -0
  59. package/dist/lib/partial-json-parser.js +142 -0
  60. package/dist/lib/partial-json-parser.js.map +1 -0
  61. package/dist/lib/partial-json-parser.mjs +137 -0
  62. package/dist/lib/token-estimator.d.ts.map +1 -1
  63. package/dist/lib/token-estimator.js +3 -2
  64. package/dist/lib/token-estimator.js.map +1 -1
  65. package/dist/lib/token-estimator.mjs +3 -2
  66. package/dist/lib/types.d.ts +20 -4
  67. package/dist/lib/types.d.ts.map +1 -1
  68. package/package.json +1 -1
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ /**
3
+ * Capability cache for model feature detection.
4
+ * Caches model capabilities per client/baseUrl to avoid redundant probing.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { capabilityCache } from './capability-cache';
9
+ *
10
+ * // Check if model supports streaming JSON
11
+ * const supports = capabilityCache.get(client, 'gemini-2.5-flash', 'stream_json_schema');
12
+ *
13
+ * // Cache result after probing
14
+ * capabilityCache.set(client, 'gemini-2.5-flash', 'stream_json_schema', true);
15
+ * ```
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.capabilityCache = void 0;
19
+ exports.createCapabilityCache = createCapabilityCache;
20
+ /**
21
+ * Default TTL for cache entries (1 hour).
22
+ */
23
+ const DEFAULT_TTL_MS = 60 * 60 * 1000;
24
+ /**
25
+ * Capability cache implementation.
26
+ * Scoped by baseUrl + model + capability to avoid cross-provider contamination.
27
+ */
28
+ class CapabilityCache {
29
+ constructor(ttlMs = DEFAULT_TTL_MS) {
30
+ this.cache = new Map();
31
+ this.ttlMs = ttlMs;
32
+ }
33
+ /**
34
+ * Generate cache key from client, model, and capability.
35
+ */
36
+ getKey(client, model, capability) {
37
+ const baseUrl = client.getBaseUrl();
38
+ return `${baseUrl}:${model}:${capability}`;
39
+ }
40
+ /**
41
+ * Get cached capability status.
42
+ * Returns undefined if not cached or expired.
43
+ */
44
+ get(client, model, capability) {
45
+ const key = this.getKey(client, model, capability);
46
+ const entry = this.cache.get(key);
47
+ if (!entry) {
48
+ return undefined;
49
+ }
50
+ // Check expiration
51
+ if (Date.now() - entry.timestamp > this.ttlMs) {
52
+ this.cache.delete(key);
53
+ return undefined;
54
+ }
55
+ return entry.supported;
56
+ }
57
+ /**
58
+ * Set capability status.
59
+ */
60
+ set(client, model, capability, supported) {
61
+ const key = this.getKey(client, model, capability);
62
+ this.cache.set(key, {
63
+ supported,
64
+ timestamp: Date.now(),
65
+ });
66
+ }
67
+ /**
68
+ * Check if capability is cached and supported.
69
+ */
70
+ isSupported(client, model, capability) {
71
+ return this.get(client, model, capability) === true;
72
+ }
73
+ /**
74
+ * Check if capability is cached and not supported.
75
+ */
76
+ isNotSupported(client, model, capability) {
77
+ return this.get(client, model, capability) === false;
78
+ }
79
+ /**
80
+ * Clear all cached entries.
81
+ */
82
+ clear() {
83
+ this.cache.clear();
84
+ }
85
+ /**
86
+ * Clear entries for a specific client.
87
+ */
88
+ clearForClient(client) {
89
+ const baseUrl = client.getBaseUrl();
90
+ const keysToDelete = [];
91
+ for (const key of this.cache.keys()) {
92
+ if (key.startsWith(baseUrl + ':')) {
93
+ keysToDelete.push(key);
94
+ }
95
+ }
96
+ for (const key of keysToDelete) {
97
+ this.cache.delete(key);
98
+ }
99
+ }
100
+ /**
101
+ * Get cache size.
102
+ */
103
+ size() {
104
+ return this.cache.size;
105
+ }
106
+ }
107
+ /**
108
+ * Global capability cache instance.
109
+ */
110
+ exports.capabilityCache = new CapabilityCache();
111
+ /**
112
+ * Create a new capability cache with custom TTL (for testing).
113
+ */
114
+ function createCapabilityCache(ttlMs) {
115
+ return new CapabilityCache(ttlMs);
116
+ }
117
+ //# sourceMappingURL=capability-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability-cache.js","sourceRoot":"","sources":["../../src/lib/capability-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Capability cache for model feature detection.
3
+ * Caches model capabilities per client/baseUrl to avoid redundant probing.
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * import { capabilityCache } from './capability-cache.mjs';
8
+ *
9
+ * // Check if model supports streaming JSON
10
+ * const supports = capabilityCache.get(client, 'gemini-2.5-flash', 'stream_json_schema');
11
+ *
12
+ * // Cache result after probing
13
+ * capabilityCache.set(client, 'gemini-2.5-flash', 'stream_json_schema', true);
14
+ * ```
15
+ */
16
+ /**
17
+ * Default TTL for cache entries (1 hour).
18
+ */
19
+ const DEFAULT_TTL_MS = 60 * 60 * 1000;
20
+ /**
21
+ * Capability cache implementation.
22
+ * Scoped by baseUrl + model + capability to avoid cross-provider contamination.
23
+ */
24
+ class CapabilityCache {
25
+ constructor(ttlMs = DEFAULT_TTL_MS) {
26
+ this.cache = new Map();
27
+ this.ttlMs = ttlMs;
28
+ }
29
+ /**
30
+ * Generate cache key from client, model, and capability.
31
+ */
32
+ getKey(client, model, capability) {
33
+ const baseUrl = client.getBaseUrl();
34
+ return `${baseUrl}:${model}:${capability}`;
35
+ }
36
+ /**
37
+ * Get cached capability status.
38
+ * Returns undefined if not cached or expired.
39
+ */
40
+ get(client, model, capability) {
41
+ const key = this.getKey(client, model, capability);
42
+ const entry = this.cache.get(key);
43
+ if (!entry) {
44
+ return undefined;
45
+ }
46
+ // Check expiration
47
+ if (Date.now() - entry.timestamp > this.ttlMs) {
48
+ this.cache.delete(key);
49
+ return undefined;
50
+ }
51
+ return entry.supported;
52
+ }
53
+ /**
54
+ * Set capability status.
55
+ */
56
+ set(client, model, capability, supported) {
57
+ const key = this.getKey(client, model, capability);
58
+ this.cache.set(key, {
59
+ supported,
60
+ timestamp: Date.now(),
61
+ });
62
+ }
63
+ /**
64
+ * Check if capability is cached and supported.
65
+ */
66
+ isSupported(client, model, capability) {
67
+ return this.get(client, model, capability) === true;
68
+ }
69
+ /**
70
+ * Check if capability is cached and not supported.
71
+ */
72
+ isNotSupported(client, model, capability) {
73
+ return this.get(client, model, capability) === false;
74
+ }
75
+ /**
76
+ * Clear all cached entries.
77
+ */
78
+ clear() {
79
+ this.cache.clear();
80
+ }
81
+ /**
82
+ * Clear entries for a specific client.
83
+ */
84
+ clearForClient(client) {
85
+ const baseUrl = client.getBaseUrl();
86
+ const keysToDelete = [];
87
+ for (const key of this.cache.keys()) {
88
+ if (key.startsWith(baseUrl + ':')) {
89
+ keysToDelete.push(key);
90
+ }
91
+ }
92
+ for (const key of keysToDelete) {
93
+ this.cache.delete(key);
94
+ }
95
+ }
96
+ /**
97
+ * Get cache size.
98
+ */
99
+ size() {
100
+ return this.cache.size;
101
+ }
102
+ }
103
+ /**
104
+ * Global capability cache instance.
105
+ */
106
+ export const capabilityCache = new CapabilityCache();
107
+ /**
108
+ * Create a new capability cache with custom TTL (for testing).
109
+ */
110
+ export function createCapabilityCache(ttlMs) {
111
+ return new CapabilityCache(ttlMs);
112
+ }
113
+ //# sourceMappingURL=capability-cache.js.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Content converter for multimodal messages.
3
+ * Normalizes SDK convenience formats to API-compatible formats.
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * import { normalizeContent } from './content-converter';
8
+ *
9
+ * const content: ContentPart[] = [
10
+ * { type: 'text', text: 'Describe this image' },
11
+ * { type: 'image', image: fs.readFileSync('photo.jpg') },
12
+ * ];
13
+ *
14
+ * const normalized = normalizeContent(content);
15
+ * // [{ type: 'text', text: '...' }, { type: 'image_url', image_url: { url: 'data:...' } }]
16
+ * ```
17
+ */
18
+ import type { ContentPart } from './types';
19
+ /**
20
+ * Normalize content parts for API calls.
21
+ * Converts `image` sugar format to `image_url` API format.
22
+ */
23
+ export declare function normalizeContent(content: string | ContentPart[]): string | ContentPart[];
24
+ /**
25
+ * Helper to convert Blob to data URL asynchronously (for browser use).
26
+ * Only available in browser environments.
27
+ */
28
+ export declare function blobToDataUrl(blob: Blob): Promise<string>;
29
+ /**
30
+ * Check if content contains any image parts that need normalization.
31
+ */
32
+ export declare function hasImageParts(content: string | ContentPart[]): boolean;
33
+ //# sourceMappingURL=content-converter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-converter.d.ts","sourceRoot":"","sources":["../../src/lib/content-converter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAoC,MAAM,SAAS,CAAC;AAE7E;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,EAAE,GAAG,MAAM,GAAG,WAAW,EAAE,CAQxF;AA+HD;;;GAGG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAa/D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,EAAE,GAAG,OAAO,CAKtE"}
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ /**
3
+ * Content converter for multimodal messages.
4
+ * Normalizes SDK convenience formats to API-compatible formats.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { normalizeContent } from './content-converter';
9
+ *
10
+ * const content: ContentPart[] = [
11
+ * { type: 'text', text: 'Describe this image' },
12
+ * { type: 'image', image: fs.readFileSync('photo.jpg') },
13
+ * ];
14
+ *
15
+ * const normalized = normalizeContent(content);
16
+ * // [{ type: 'text', text: '...' }, { type: 'image_url', image_url: { url: 'data:...' } }]
17
+ * ```
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.normalizeContent = normalizeContent;
21
+ exports.blobToDataUrl = blobToDataUrl;
22
+ exports.hasImageParts = hasImageParts;
23
+ /**
24
+ * Normalize content parts for API calls.
25
+ * Converts `image` sugar format to `image_url` API format.
26
+ */
27
+ function normalizeContent(content) {
28
+ // String content doesn't need normalization
29
+ if (typeof content === 'string') {
30
+ return content;
31
+ }
32
+ // Normalize each content part
33
+ return content.map(part => normalizeContentPart(part));
34
+ }
35
+ /**
36
+ * Normalize a single content part.
37
+ */
38
+ function normalizeContentPart(part) {
39
+ // Already in API format
40
+ if (part.type === 'text' || part.type === 'image_url') {
41
+ return part;
42
+ }
43
+ // Convert image sugar to image_url
44
+ if (part.type === 'image') {
45
+ return {
46
+ type: 'image_url',
47
+ image_url: {
48
+ url: imageSourceToDataUrl(part.image),
49
+ detail: part.detail,
50
+ },
51
+ };
52
+ }
53
+ // Unknown type, return as-is
54
+ return part;
55
+ }
56
+ /**
57
+ * Convert ImageSource to data URL.
58
+ */
59
+ function imageSourceToDataUrl(source) {
60
+ // Already a string (base64 or URL)
61
+ if (typeof source === 'string') {
62
+ // Check if it's already a data URL or regular URL
63
+ if (source.startsWith('data:') || source.startsWith('http://') || source.startsWith('https://')) {
64
+ return source;
65
+ }
66
+ // Assume base64, wrap in data URL
67
+ return `data:image/png;base64,${source}`;
68
+ }
69
+ // URL object
70
+ if (source instanceof URL) {
71
+ return source.toString();
72
+ }
73
+ // Blob (browser)
74
+ if (typeof Blob !== 'undefined' && source instanceof Blob) {
75
+ // Note: Synchronous conversion not possible for Blob
76
+ // For real usage, caller should pre-convert to base64
77
+ throw new Error('Blob must be converted to base64 before passing to normalizeContent. Use blobToDataUrl() helper.');
78
+ }
79
+ // ArrayBuffer or Uint8Array
80
+ if (source instanceof ArrayBuffer) {
81
+ return arrayBufferToDataUrl(new Uint8Array(source));
82
+ }
83
+ if (source instanceof Uint8Array) {
84
+ return arrayBufferToDataUrl(source);
85
+ }
86
+ throw new Error(`Unsupported image source type: ${typeof source}`);
87
+ }
88
+ /**
89
+ * Convert Uint8Array to data URL.
90
+ */
91
+ function arrayBufferToDataUrl(buffer) {
92
+ // Detect MIME type from magic bytes
93
+ const mimeType = detectMimeType(buffer);
94
+ // Convert to base64
95
+ const base64 = uint8ArrayToBase64(buffer);
96
+ return `data:${mimeType};base64,${base64}`;
97
+ }
98
+ /**
99
+ * Detect image MIME type from magic bytes.
100
+ */
101
+ function detectMimeType(buffer) {
102
+ if (buffer.length < 4) {
103
+ return 'application/octet-stream';
104
+ }
105
+ // PNG: 89 50 4E 47
106
+ if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4E && buffer[3] === 0x47) {
107
+ return 'image/png';
108
+ }
109
+ // JPEG: FF D8 FF
110
+ if (buffer[0] === 0xFF && buffer[1] === 0xD8 && buffer[2] === 0xFF) {
111
+ return 'image/jpeg';
112
+ }
113
+ // GIF: 47 49 46 38
114
+ if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x38) {
115
+ return 'image/gif';
116
+ }
117
+ // WebP: 52 49 46 46 ... 57 45 42 50
118
+ if (buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46 &&
119
+ buffer.length > 11 && buffer[8] === 0x57 && buffer[9] === 0x45 && buffer[10] === 0x42 && buffer[11] === 0x50) {
120
+ return 'image/webp';
121
+ }
122
+ return 'image/png'; // Default fallback
123
+ }
124
+ /**
125
+ * Convert Uint8Array to base64 string.
126
+ * Works in both Node.js and browser environments.
127
+ */
128
+ function uint8ArrayToBase64(buffer) {
129
+ // Node.js environment
130
+ if (typeof Buffer !== 'undefined') {
131
+ return Buffer.from(buffer).toString('base64');
132
+ }
133
+ // Browser environment
134
+ let binary = '';
135
+ for (let i = 0; i < buffer.length; i++) {
136
+ binary += String.fromCharCode(buffer[i]);
137
+ }
138
+ return btoa(binary);
139
+ }
140
+ /**
141
+ * Helper to convert Blob to data URL asynchronously (for browser use).
142
+ * Only available in browser environments.
143
+ */
144
+ async function blobToDataUrl(blob) {
145
+ // Check if FileReader is available (browser environment)
146
+ const FR = globalThis.FileReader;
147
+ if (!FR) {
148
+ throw new Error('blobToDataUrl is only available in browser environments');
149
+ }
150
+ return new Promise((resolve, reject) => {
151
+ const reader = new FR();
152
+ reader.onloadend = () => resolve(reader.result);
153
+ reader.onerror = reject;
154
+ reader.readAsDataURL(blob);
155
+ });
156
+ }
157
+ /**
158
+ * Check if content contains any image parts that need normalization.
159
+ */
160
+ function hasImageParts(content) {
161
+ if (typeof content === 'string') {
162
+ return false;
163
+ }
164
+ return content.some(part => part.type === 'image');
165
+ }
166
+ //# sourceMappingURL=content-converter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-converter.js","sourceRoot":"","sources":["../../src/lib/content-converter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Content converter for multimodal messages.
3
+ * Normalizes SDK convenience formats to API-compatible formats.
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * import { normalizeContent } from './content-converter.mjs';
8
+ *
9
+ * const content: ContentPart[] = [
10
+ * { type: 'text', text: 'Describe this image' },
11
+ * { type: 'image', image: fs.readFileSync('photo.jpg') },
12
+ * ];
13
+ *
14
+ * const normalized = normalizeContent(content);
15
+ * // [{ type: 'text', text: '...' }, { type: 'image_url', image_url: { url: 'data:...' } }]
16
+ * ```
17
+ */
18
+ /**
19
+ * Normalize content parts for API calls.
20
+ * Converts `image` sugar format to `image_url` API format.
21
+ */
22
+ export function normalizeContent(content) {
23
+ // String content doesn't need normalization
24
+ if (typeof content === 'string') {
25
+ return content;
26
+ }
27
+ // Normalize each content part
28
+ return content.map(part => normalizeContentPart(part));
29
+ }
30
+ /**
31
+ * Normalize a single content part.
32
+ */
33
+ function normalizeContentPart(part) {
34
+ // Already in API format
35
+ if (part.type === 'text' || part.type === 'image_url') {
36
+ return part;
37
+ }
38
+ // Convert image sugar to image_url
39
+ if (part.type === 'image') {
40
+ return {
41
+ type: 'image_url',
42
+ image_url: {
43
+ url: imageSourceToDataUrl(part.image),
44
+ detail: part.detail,
45
+ },
46
+ };
47
+ }
48
+ // Unknown type, return as-is
49
+ return part;
50
+ }
51
+ /**
52
+ * Convert ImageSource to data URL.
53
+ */
54
+ function imageSourceToDataUrl(source) {
55
+ // Already a string (base64 or URL)
56
+ if (typeof source === 'string') {
57
+ // Check if it's already a data URL or regular URL
58
+ if (source.startsWith('data:') || source.startsWith('http://') || source.startsWith('https://')) {
59
+ return source;
60
+ }
61
+ // Assume base64, wrap in data URL
62
+ return `data:image/png;base64,${source}`;
63
+ }
64
+ // URL object
65
+ if (source instanceof URL) {
66
+ return source.toString();
67
+ }
68
+ // Blob (browser)
69
+ if (typeof Blob !== 'undefined' && source instanceof Blob) {
70
+ // Note: Synchronous conversion not possible for Blob
71
+ // For real usage, caller should pre-convert to base64
72
+ throw new Error('Blob must be converted to base64 before passing to normalizeContent. Use blobToDataUrl() helper.');
73
+ }
74
+ // ArrayBuffer or Uint8Array
75
+ if (source instanceof ArrayBuffer) {
76
+ return arrayBufferToDataUrl(new Uint8Array(source));
77
+ }
78
+ if (source instanceof Uint8Array) {
79
+ return arrayBufferToDataUrl(source);
80
+ }
81
+ throw new Error(`Unsupported image source type: ${typeof source}`);
82
+ }
83
+ /**
84
+ * Convert Uint8Array to data URL.
85
+ */
86
+ function arrayBufferToDataUrl(buffer) {
87
+ // Detect MIME type from magic bytes
88
+ const mimeType = detectMimeType(buffer);
89
+ // Convert to base64
90
+ const base64 = uint8ArrayToBase64(buffer);
91
+ return `data:${mimeType};base64,${base64}`;
92
+ }
93
+ /**
94
+ * Detect image MIME type from magic bytes.
95
+ */
96
+ function detectMimeType(buffer) {
97
+ if (buffer.length < 4) {
98
+ return 'application/octet-stream';
99
+ }
100
+ // PNG: 89 50 4E 47
101
+ if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4E && buffer[3] === 0x47) {
102
+ return 'image/png';
103
+ }
104
+ // JPEG: FF D8 FF
105
+ if (buffer[0] === 0xFF && buffer[1] === 0xD8 && buffer[2] === 0xFF) {
106
+ return 'image/jpeg';
107
+ }
108
+ // GIF: 47 49 46 38
109
+ if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x38) {
110
+ return 'image/gif';
111
+ }
112
+ // WebP: 52 49 46 46 ... 57 45 42 50
113
+ if (buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46 &&
114
+ buffer.length > 11 && buffer[8] === 0x57 && buffer[9] === 0x45 && buffer[10] === 0x42 && buffer[11] === 0x50) {
115
+ return 'image/webp';
116
+ }
117
+ return 'image/png'; // Default fallback
118
+ }
119
+ /**
120
+ * Convert Uint8Array to base64 string.
121
+ * Works in both Node.js and browser environments.
122
+ */
123
+ function uint8ArrayToBase64(buffer) {
124
+ // Node.js environment
125
+ if (typeof Buffer !== 'undefined') {
126
+ return Buffer.from(buffer).toString('base64');
127
+ }
128
+ // Browser environment
129
+ let binary = '';
130
+ for (let i = 0; i < buffer.length; i++) {
131
+ binary += String.fromCharCode(buffer[i]);
132
+ }
133
+ return btoa(binary);
134
+ }
135
+ /**
136
+ * Helper to convert Blob to data URL asynchronously (for browser use).
137
+ * Only available in browser environments.
138
+ */
139
+ export async function blobToDataUrl(blob) {
140
+ // Check if FileReader is available (browser environment)
141
+ const FR = globalThis.FileReader;
142
+ if (!FR) {
143
+ throw new Error('blobToDataUrl is only available in browser environments');
144
+ }
145
+ return new Promise((resolve, reject) => {
146
+ const reader = new FR();
147
+ reader.onloadend = () => resolve(reader.result);
148
+ reader.onerror = reject;
149
+ reader.readAsDataURL(blob);
150
+ });
151
+ }
152
+ /**
153
+ * Check if content contains any image parts that need normalization.
154
+ */
155
+ export function hasImageParts(content) {
156
+ if (typeof content === 'string') {
157
+ return false;
158
+ }
159
+ return content.some(part => part.type === 'image');
160
+ }
161
+ //# sourceMappingURL=content-converter.js.map
@@ -49,10 +49,11 @@ function estimateMessageTokens(message) {
49
49
  return 0;
50
50
  }
51
51
  function estimateTokensFromPart(part) {
52
- if (part.type === 'text') {
53
- return estimateTokensFromText(part.text ?? '');
52
+ if (part.type === 'text' && 'text' in part) {
53
+ return estimateTokensFromText(part.text);
54
54
  }
55
- if (part.type === 'image_url') {
55
+ // Both image_url (API format) and image (SDK sugar) count as image tokens
56
+ if (part.type === 'image_url' || part.type === 'image') {
56
57
  return 50;
57
58
  }
58
59
  return 0;
@@ -1 +1 @@
1
- {"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/lib/messages.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/lib/messages.ts"],"names":[],"mappings":"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"}
@@ -45,10 +45,11 @@ function estimateMessageTokens(message) {
45
45
  return 0;
46
46
  }
47
47
  function estimateTokensFromPart(part) {
48
- if (part.type === 'text') {
49
- return estimateTokensFromText(part.text ?? '');
48
+ if (part.type === 'text' && 'text' in part) {
49
+ return estimateTokensFromText(part.text);
50
50
  }
51
- if (part.type === 'image_url') {
51
+ // Both image_url (API format) and image (SDK sugar) count as image tokens
52
+ if (part.type === 'image_url' || part.type === 'image') {
52
53
  return 50;
53
54
  }
54
55
  return 0;