11import type { StreamedMessagePart , ToolCall } from '#/kosong/contract/message' ;
22
3- const CANDIDATE_TARGETS = [
4- 'dsml|tool_calls' ,
5- 'dsml|tool_calls' ,
6- 'dsmltool_calls' ,
7- 'dsml|invoke' ,
8- 'dsml|invoke' ,
9- 'dsmlinvoke' ,
10- 'tool_calls' ,
11- 'tool_call' ,
12- 'invoke' ,
13- ] ;
14-
153const CONTAINER_OPEN_RE = / ^ < \s * [ | | ] ? \s * (?: D S M L \s * [ | | ] ? ) ? \s * t o o l _ c a l l s \s * > / i;
164const CONTAINER_CLOSE_RE = / ^ < \/ \s * [ | | ] ? \s * (?: D S M L \s * [ | | ] ? ) ? \s * t o o l _ c a l l s \s * > / i;
175const INVOKE_OPEN_RE = / ^ < \s * [ | | ] ? \s * (?: D S M L \s * [ | | ] ? ) ? \s * i n v o k e (?: \s + [ ^ > ] * ) ? > / i;
@@ -110,9 +98,8 @@ function parseInvokeTag(invokeBlock: string): ToolCall | null {
11098 if ( ! toolName ) return null ;
11199
112100 const closeMatch = INVOKE_CLOSE_RE . exec ( invokeBlock ) ;
113- const innerContent = closeMatch
114- ? invokeBlock . slice ( openMatch [ 0 ] . length , closeMatch . index )
115- : invokeBlock . slice ( openMatch [ 0 ] . length ) ;
101+ if ( ! closeMatch ) return null ;
102+ const innerContent = invokeBlock . slice ( openMatch [ 0 ] . length , closeMatch . index ) ;
116103
117104 const args = parseInvokeBody ( innerContent ) ;
118105 return {
@@ -124,6 +111,7 @@ function parseInvokeTag(invokeBlock: string): ToolCall | null {
124111}
125112
126113function parseHermesToolCall ( toolCallBlock : string ) : ToolCall | null {
114+ if ( ! HERMES_CLOSE_RE . test ( toolCallBlock ) ) return null ;
127115 const inner = toolCallBlock
128116 . replace ( / ^ < t o o l _ c a l l > / i, '' )
129117 . replace ( / < \/ t o o l _ c a l l > $ / i, '' )
@@ -150,11 +138,19 @@ function isPotentialTagPrefix(s: string): boolean {
150138 if ( ! s . startsWith ( '<' ) ) return false ;
151139 const lower = s . toLowerCase ( ) ;
152140 let rest = lower . startsWith ( '</' ) ? lower . slice ( 2 ) : lower . slice ( 1 ) ;
141+ rest = rest . trimStart ( ) ;
153142 if ( rest . startsWith ( '|' ) || rest . startsWith ( '|' ) ) {
154- rest = rest . slice ( 1 ) ;
143+ rest = rest . slice ( 1 ) . trimStart ( ) ;
144+ }
145+ if ( rest . startsWith ( 'dsml' ) ) {
146+ rest = rest . slice ( 4 ) . trimStart ( ) ;
147+ if ( rest . startsWith ( '|' ) || rest . startsWith ( '|' ) ) {
148+ rest = rest . slice ( 1 ) . trimStart ( ) ;
149+ }
155150 }
156151 if ( rest . length === 0 ) return true ;
157- return CANDIDATE_TARGETS . some ( ( target ) => target . startsWith ( rest ) || rest . startsWith ( target ) ) ;
152+ const targets = [ 'tool_calls' , 'tool_call' , 'invoke' , 'parameter' ] ;
153+ return targets . some ( ( t ) => t . startsWith ( rest ) || rest . startsWith ( t ) ) ;
158154}
159155
160156export class DsmlStreamParser {
@@ -276,7 +272,7 @@ export class DsmlStreamParser {
276272 const parts : StreamedMessagePart [ ] = [ ] ;
277273 if ( this . _buffer . length > 0 ) {
278274 const invokeOpen = INVOKE_OPEN_RE . exec ( this . _buffer ) ;
279- if ( invokeOpen ) {
275+ if ( invokeOpen && INVOKE_CLOSE_RE . test ( this . _buffer ) ) {
280276 const toolCall = parseInvokeTag ( this . _buffer ) ;
281277 if ( toolCall ) {
282278 this . _hasExtractedToolCalls = true ;
@@ -286,7 +282,7 @@ export class DsmlStreamParser {
286282 }
287283 }
288284 const hermesOpen = HERMES_OPEN_RE . exec ( this . _buffer ) ;
289- if ( hermesOpen ) {
285+ if ( hermesOpen && HERMES_CLOSE_RE . test ( this . _buffer ) ) {
290286 const toolCall = parseHermesToolCall ( this . _buffer ) ;
291287 if ( toolCall ) {
292288 this . _hasExtractedToolCalls = true ;
0 commit comments