@@ -498,9 +498,101 @@ function gemini(req, opts) {
498498 } ) . catch ( error => console . error ( 'Error:' , error ) ) ;
499499}
500500
501+ function custom ( req , opts ) {
502+ const decoder = new TextDecoder ( ) ;
503+ const abortCtrl = new AbortController ( ) ;
504+
505+ if ( ! custom . serviceUrl ) {
506+ opts . onChunk ( 'Please set service URL correctly.' ) ;
507+ opts . onComplete ( { } ) ;
508+ return ;
509+ }
510+ if ( ! custom . apiKey ) {
511+ opts . onChunk ( 'Please set API key correctly.' ) ;
512+ opts . onComplete ( { } ) ;
513+ return ;
514+ }
515+ if ( ! custom . model ) {
516+ opts . onChunk ( 'Please set model correctly.' ) ;
517+ opts . onComplete ( { } ) ;
518+ return ;
519+ }
520+
521+ const transformMessages = msgs => msgs . map ( m =>
522+ typeof m . content === 'string' ? m : { role : m . role , content : m . content [ 0 ] . text }
523+ ) ;
524+
525+ fetch ( custom . serviceUrl , {
526+ method : 'POST' ,
527+ headers : {
528+ Authorization : `Bearer ${ custom . apiKey } ` ,
529+ 'Content-Type' : 'application/json' ,
530+ } ,
531+ body : JSON . stringify ( {
532+ model : custom . model ,
533+ stream : true ,
534+ messages : transformMessages ( req . messages ) ,
535+ } ) ,
536+ signal : abortCtrl . signal ,
537+ } )
538+ . then ( resp => {
539+ const reader = resp . body . getReader ( ) ;
540+ let contentBlock = { type : 'text' , text : '' } ;
541+
542+ const readStream = ( ) => {
543+ reader . read ( )
544+ . then ( ( { done, value } ) => {
545+ if ( done ) {
546+ return ;
547+ }
548+ const chunk = decoder . decode ( value ) ;
549+ try {
550+ const lines = chunk . trim ( ) . split ( '\n\n' ) ;
551+ const dataPat = / ^ d a t a : / ;
552+ for ( const line of lines ) {
553+ if ( ! dataPat . test ( line ) ) {
554+ continue ;
555+ }
556+ const data = line . replace ( dataPat , '' ) ;
557+ if ( data === '[DONE]' ) {
558+ opts . onComplete ( { role : 'assistant' , content : [ contentBlock ] } ) ;
559+ return ;
560+ }
561+ const o = JSON . parse ( data ) ;
562+ if ( o . choices ?. [ 0 ] ?. delta ?. content ) {
563+ const txt = o . choices [ 0 ] . delta . content ;
564+ opts . onChunk ( txt ) ;
565+ contentBlock . text += txt ;
566+ }
567+ }
568+ } catch ( e ) {
569+ console . error ( 'Error parsing chunk:' , e ) ;
570+ }
571+
572+ readStream ( ) ;
573+ } )
574+ . catch ( err => {
575+ if ( err . name !== 'AbortError' ) {
576+ console . error ( 'Stream error:' , err ) ;
577+ }
578+ } ) ;
579+ } ;
580+
581+ readStream ( ) ;
582+ } )
583+ . catch ( err => {
584+ if ( err . name !== 'AbortError' ) {
585+ console . error ( 'Fetch error:' , err ) ;
586+ }
587+ } ) ;
588+
589+ return ( ) => abortCtrl . abort ( ) ;
590+ }
591+
501592export default {
502593 bedrock,
503594 deepseek,
504595 gemini,
505596 ollama,
597+ custom,
506598}
0 commit comments