@@ -20,6 +20,10 @@ const BASE64_IMAGE_URL_REGEX =
20
20
const HTTP_IMAGE_URL_REGEX =
21
21
/ h t t p s ? : \/ \/ [ ^ \s " ' < > ] + \. (?< format > p n g | j p e g | j p g | g i f | w e b p | s v g ) [ ^ \s " ' < > ] * / i;
22
22
23
+ // Regular expression to match HTTP/HTTPS audio links
24
+ const HTTP_AUDIO_URL_REGEX =
25
+ / h t t p s ? : \/ \/ [ ^ \s " ' < > ] + \. (?< format > m p 3 | w a v | o g g | f l a c | m 4 a | a a c ) [ ^ \s " ' < > ] * / i;
26
+
23
27
/**
24
28
* Utility function to safely extract content from regex matches
25
29
* @param content The content to extract from
@@ -65,6 +69,27 @@ const extractImageUrl = (
65
69
return { url : undefined , format : undefined , isHttp : false } ;
66
70
} ;
67
71
72
+ /**
73
+ * Extract audio URL from a string
74
+ * @param str The string to search in
75
+ * @returns The found audio URL, format and whether it's an HTTP link
76
+ */
77
+ const extractAudioUrl = (
78
+ str : string ,
79
+ ) : { url : string | undefined ; format : string | undefined ; isHttp : boolean } => {
80
+ // Check if it contains an HTTP audio URL
81
+ const httpMatch = HTTP_AUDIO_URL_REGEX . exec ( str ) ;
82
+ if ( httpMatch ?. groups && httpMatch [ 0 ] ) {
83
+ return {
84
+ url : httpMatch [ 0 ] ,
85
+ format : httpMatch . groups . format ,
86
+ isHttp : true ,
87
+ } ;
88
+ }
89
+
90
+ return { url : undefined , format : undefined , isHttp : false } ;
91
+ } ;
92
+
68
93
/**
69
94
* Rehype plugin to process tool_use tags in markdown
70
95
* When parsing <tool_use> tags, if a <result> exists, extract both <arguments> and <result> and put them on the same node property.
@@ -106,6 +131,12 @@ function rehypePlugin() {
106
131
let isHttpUrl = false ;
107
132
let imageNameFromArgs = 'image' ; // Default image name
108
133
134
+ // Attempt to find and process audio data in the result
135
+ let audioUrlFromDetails : string | undefined ;
136
+ let audioFormatFromDetails : string | undefined ;
137
+ let _isAudioHttpUrl = false ;
138
+ let audioNameFromArgs = 'audio' ; // Default audio name
139
+
109
140
// 1. Directly search for image URL in the result string
110
141
const { url, format, isHttp } = extractImageUrl ( resultStr ) ;
111
142
if ( url ) {
@@ -129,6 +160,30 @@ function rehypePlugin() {
129
160
}
130
161
}
131
162
163
+ // 1. Directly search for audio URL in the result string
164
+ const audioResult = extractAudioUrl ( resultStr ) ;
165
+ if ( audioResult . url ) {
166
+ audioUrlFromDetails = audioResult . url ;
167
+ audioFormatFromDetails = audioResult . format ;
168
+ // isAudioHttpUrl 变量在这里不需要使用,但我们保留它以保持代码结构一致性
169
+ _isAudioHttpUrl = true ;
170
+ } else {
171
+ // 2. If direct search fails, try to parse JSON and search in the stringified JSON result
172
+ try {
173
+ const resultObj = JSON . parse ( resultStr ) ;
174
+ const resultJsonStr = JSON . stringify ( resultObj ) ;
175
+ const jsonAudioResult = extractAudioUrl ( resultJsonStr ) ;
176
+
177
+ if ( jsonAudioResult . url ) {
178
+ audioUrlFromDetails = jsonAudioResult . url ;
179
+ audioFormatFromDetails = jsonAudioResult . format ;
180
+ _isAudioHttpUrl = jsonAudioResult . isHttp ;
181
+ }
182
+ } catch ( _e ) {
183
+ // Not a JSON result, or JSON parsing failed
184
+ }
185
+ }
186
+
132
187
if ( imageUrlFromDetails && imageFormatFromDetails ) {
133
188
// Set different attributes based on whether it's an HTTP link or not
134
189
if ( isHttpUrl ) {
@@ -165,6 +220,40 @@ function rehypePlugin() {
165
220
attributes [ 'data-tool-image-name' ] =
166
221
`${ imageNameFromArgs } .${ imageFormatFromDetails } ` ;
167
222
}
223
+
224
+ // Handle audio URL if found
225
+ if ( audioUrlFromDetails && audioFormatFromDetails ) {
226
+ // Set audio URL attribute
227
+ attributes [ 'data-tool-audio-http-url' ] = audioUrlFromDetails ;
228
+
229
+ // Attempt to get audio name from arguments
230
+ if ( argsStr ) {
231
+ try {
232
+ const argsObj = JSON . parse ( argsStr ) ;
233
+ if ( typeof argsObj . params === 'string' ) {
234
+ const paramsObj = JSON . parse ( argsObj . params ) ;
235
+ if ( paramsObj && typeof paramsObj . name === 'string' ) {
236
+ const trimmedName = paramsObj . name . trim ( ) ;
237
+ if ( trimmedName ) {
238
+ // Ensure non-empty name after trimming
239
+ audioNameFromArgs = trimmedName ;
240
+ }
241
+ }
242
+ } else if ( argsObj && typeof argsObj . name === 'string' ) {
243
+ const trimmedName = argsObj . name . trim ( ) ;
244
+ if ( trimmedName ) {
245
+ // Ensure non-empty name after trimming
246
+ audioNameFromArgs = trimmedName ;
247
+ }
248
+ }
249
+ } catch ( _e ) {
250
+ // Argument parsing failed
251
+ }
252
+ }
253
+ attributes [ 'data-tool-audio-name' ] =
254
+ `${ audioNameFromArgs } .${ audioFormatFromDetails } ` ;
255
+ attributes [ 'data-tool-audio-format' ] = audioFormatFromDetails ;
256
+ }
168
257
}
169
258
170
259
// Create a new node with the extracted data for tool_use
@@ -250,6 +339,12 @@ function rehypePlugin() {
250
339
let isHttpUrl = false ;
251
340
let imageNameFromArgs = 'image' ; // Default image name
252
341
342
+ // Attempt to find and process audio data in the result
343
+ let audioUrlFromDetails : string | undefined ;
344
+ let audioFormatFromDetails : string | undefined ;
345
+ let _isAudioHttpUrl = false ;
346
+ let audioNameFromArgs = 'audio' ; // Default audio name
347
+
253
348
// Directly search for image URL in the result string
254
349
const { url, format, isHttp } = extractImageUrl ( resultStr ) ;
255
350
if ( url ) {
@@ -273,6 +368,30 @@ function rehypePlugin() {
273
368
}
274
369
}
275
370
371
+ // Directly search for audio URL in the result string
372
+ const audioResult = extractAudioUrl ( resultStr ) ;
373
+ if ( audioResult . url ) {
374
+ audioUrlFromDetails = audioResult . url ;
375
+ audioFormatFromDetails = audioResult . format ;
376
+ // isAudioHttpUrl 变量在这里不需要使用,但我们保留它以保持代码结构一致性
377
+ _isAudioHttpUrl = true ;
378
+ } else {
379
+ // If direct search fails, try to parse JSON and search in the stringified JSON result
380
+ try {
381
+ const resultObj = JSON . parse ( resultStr ) ;
382
+ const resultJsonStr = JSON . stringify ( resultObj ) ;
383
+ const jsonAudioResult = extractAudioUrl ( resultJsonStr ) ;
384
+
385
+ if ( jsonAudioResult . url ) {
386
+ audioUrlFromDetails = jsonAudioResult . url ;
387
+ audioFormatFromDetails = jsonAudioResult . format ;
388
+ _isAudioHttpUrl = jsonAudioResult . isHttp ;
389
+ }
390
+ } catch ( _e ) {
391
+ // Not a JSON result, or JSON parsing failed
392
+ }
393
+ }
394
+
276
395
if ( imageUrlFromDetails && imageFormatFromDetails ) {
277
396
// Set different attributes based on whether it's an HTTP link or not
278
397
if ( isHttpUrl ) {
@@ -307,6 +426,40 @@ function rehypePlugin() {
307
426
attributes [ 'data-tool-image-name' ] =
308
427
`${ imageNameFromArgs } .${ imageFormatFromDetails } ` ;
309
428
}
429
+
430
+ // Handle audio URL if found
431
+ if ( audioUrlFromDetails && audioFormatFromDetails ) {
432
+ // Set audio URL attribute
433
+ attributes [ 'data-tool-audio-http-url' ] = audioUrlFromDetails ;
434
+
435
+ // Attempt to get audio name from arguments
436
+ if ( argsStr ) {
437
+ try {
438
+ const argsObj = JSON . parse ( argsStr ) ;
439
+ if ( typeof argsObj . params === 'string' ) {
440
+ const paramsObj = JSON . parse ( argsObj . params ) ;
441
+ if ( paramsObj && typeof paramsObj . name === 'string' ) {
442
+ const trimmedName = paramsObj . name . trim ( ) ;
443
+ if ( trimmedName ) {
444
+ // Ensure non-empty name after trimming
445
+ audioNameFromArgs = trimmedName ;
446
+ }
447
+ }
448
+ } else if ( argsObj && typeof argsObj . name === 'string' ) {
449
+ const trimmedName = argsObj . name . trim ( ) ;
450
+ if ( trimmedName ) {
451
+ // Ensure non-empty name after trimming
452
+ audioNameFromArgs = trimmedName ;
453
+ }
454
+ }
455
+ } catch ( _e ) {
456
+ // Argument parsing failed
457
+ }
458
+ }
459
+ attributes [ 'data-tool-audio-name' ] =
460
+ `${ audioNameFromArgs } .${ audioFormatFromDetails } ` ;
461
+ attributes [ 'data-tool-audio-format' ] = audioFormatFromDetails ;
462
+ }
310
463
}
311
464
312
465
// Create a new node with the extracted data for tool_use
0 commit comments