11/**
2- * Parses thinking content from a message that may contain <think> tags
2+ * Parses thinking content from a message that may contain <think> tags or [THINK] tags
33 * Returns an object with thinking content and cleaned message content
4- * Handles both complete <think>...</think> blocks and incomplete <think> blocks (streaming)
4+ * Handles both complete blocks and incomplete blocks (streaming)
5+ * Supports formats: <think>...</think> and [THINK]...[/THINK]
56 * @param content - The message content to parse
67 * @returns An object containing the extracted thinking content and the cleaned message content
78 */
89export function parseThinkingContent ( content : string ) : {
910 thinking : string | null ;
1011 cleanContent : string ;
1112} {
12- const incompleteMatch = content . includes ( '<think>' ) && ! content . includes ( '</think>' ) ;
13+ const incompleteThinkMatch = content . includes ( '<think>' ) && ! content . includes ( '</think>' ) ;
14+ const incompleteThinkBracketMatch = content . includes ( '[THINK]' ) && ! content . includes ( '[/THINK]' ) ;
1315
14- if ( incompleteMatch ) {
15- // Remove the entire <think>... part from clean content
16+ if ( incompleteThinkMatch ) {
1617 const cleanContent = content . split ( '</think>' ) ?. [ 1 ] ?. trim ( ) ;
17- // Extract everything after <think> as thinking content
1818 const thinkingContent = content . split ( '<think>' ) ?. [ 1 ] ?. trim ( ) ;
1919
2020 return {
@@ -23,12 +23,35 @@ export function parseThinkingContent(content: string): {
2323 } ;
2424 }
2525
26- const completeMatch = content . match ( / < t h i n k > ( [ \s \S ] * ?) < \/ t h i n k > / ) ;
26+ if ( incompleteThinkBracketMatch ) {
27+ const cleanContent = content . split ( '[/THINK]' ) ?. [ 1 ] ?. trim ( ) ;
28+ const thinkingContent = content . split ( '[THINK]' ) ?. [ 1 ] ?. trim ( ) ;
2729
28- if ( completeMatch ) {
29- const thinkingContent = completeMatch [ 1 ] ?. trim ( ) ?? '' ;
30- const cleanContent = `${ content . slice ( 0 , completeMatch . index ?? 0 ) } ${ content . slice (
31- ( completeMatch . index ?? 0 ) + completeMatch [ 0 ] . length
30+ return {
31+ cleanContent,
32+ thinking : thinkingContent
33+ } ;
34+ }
35+
36+ const completeThinkMatch = content . match ( / < t h i n k > ( [ \s \S ] * ?) < \/ t h i n k > / ) ;
37+ const completeThinkBracketMatch = content . match ( / \[ T H I N K \] ( [ \s \S ] * ?) \[ \/ T H I N K \] / ) ;
38+
39+ if ( completeThinkMatch ) {
40+ const thinkingContent = completeThinkMatch [ 1 ] ?. trim ( ) ?? '' ;
41+ const cleanContent = `${ content . slice ( 0 , completeThinkMatch . index ?? 0 ) } ${ content . slice (
42+ ( completeThinkMatch . index ?? 0 ) + completeThinkMatch [ 0 ] . length
43+ ) } `. trim ( ) ;
44+
45+ return {
46+ thinking : thinkingContent ,
47+ cleanContent
48+ } ;
49+ }
50+
51+ if ( completeThinkBracketMatch ) {
52+ const thinkingContent = completeThinkBracketMatch [ 1 ] ?. trim ( ) ?? '' ;
53+ const cleanContent = `${ content . slice ( 0 , completeThinkBracketMatch . index ?? 0 ) } ${ content . slice (
54+ ( completeThinkBracketMatch . index ?? 0 ) + completeThinkBracketMatch [ 0 ] . length
3255 ) } `. trim ( ) ;
3356
3457 return {
@@ -44,50 +67,75 @@ export function parseThinkingContent(content: string): {
4467}
4568
4669/**
47- * Checks if content contains an opening <think> tag (for streaming)
70+ * Checks if content contains an opening thinking tag (for streaming)
71+ * Supports both <think> and [THINK] formats
4872 * @param content - The message content to check
49- * @returns True if the content contains an opening <think> tag
73+ * @returns True if the content contains an opening thinking tag
5074 */
5175export function hasThinkingStart ( content : string ) : boolean {
52- return content . includes ( '<think>' ) || content . includes ( '<|channel|>analysis' ) ;
76+ return (
77+ content . includes ( '<think>' ) ||
78+ content . includes ( '[THINK]' ) ||
79+ content . includes ( '<|channel|>analysis' )
80+ ) ;
5381}
5482
5583/**
56- * Checks if content contains a closing </think> tag (for streaming)
84+ * Checks if content contains a closing thinking tag (for streaming)
85+ * Supports both </think> and [/THINK] formats
5786 * @param content - The message content to check
58- * @returns True if the content contains a closing </think> tag
87+ * @returns True if the content contains a closing thinking tag
5988 */
6089export function hasThinkingEnd ( content : string ) : boolean {
61- return content . includes ( '</think>' ) ;
90+ return content . includes ( '</think>' ) || content . includes ( '[/THINK]' ) ;
6291}
6392
6493/**
6594 * Extracts partial thinking content during streaming
66- * Used when we have <think> but not yet </think>
95+ * Supports both <think> and [THINK] formats
96+ * Used when we have opening tag but not yet closing tag
6797 * @param content - The message content to extract partial thinking from
6898 * @returns An object containing the extracted partial thinking content and the remaining content
6999 */
70100export function extractPartialThinking ( content : string ) : {
71101 thinking : string | null ;
72102 remainingContent : string ;
73103} {
74- const startIndex = content . indexOf ( '<think>' ) ;
75- if ( startIndex === - 1 ) {
76- return { thinking : null , remainingContent : content } ;
77- }
104+ const thinkStartIndex = content . indexOf ( '<think>' ) ;
105+ const thinkEndIndex = content . indexOf ( '</think>' ) ;
78106
79- const endIndex = content . indexOf ( '</think>' ) ;
80- if ( endIndex === - 1 ) {
81- // Still streaming thinking content
82- const thinkingStart = startIndex + '<think>' . length ;
83- return {
84- thinking : content . substring ( thinkingStart ) ,
85- remainingContent : content . substring ( 0 , startIndex )
86- } ;
107+ const bracketStartIndex = content . indexOf ( '[THINK]' ) ;
108+ const bracketEndIndex = content . indexOf ( '[/THINK]' ) ;
109+
110+ const useThinkFormat =
111+ thinkStartIndex !== - 1 && ( bracketStartIndex === - 1 || thinkStartIndex < bracketStartIndex ) ;
112+ const useBracketFormat =
113+ bracketStartIndex !== - 1 && ( thinkStartIndex === - 1 || bracketStartIndex < thinkStartIndex ) ;
114+
115+ if ( useThinkFormat ) {
116+ if ( thinkEndIndex === - 1 ) {
117+ const thinkingStart = thinkStartIndex + '<think>' . length ;
118+
119+ return {
120+ thinking : content . substring ( thinkingStart ) ,
121+ remainingContent : content . substring ( 0 , thinkStartIndex )
122+ } ;
123+ }
124+ } else if ( useBracketFormat ) {
125+ if ( bracketEndIndex === - 1 ) {
126+ const thinkingStart = bracketStartIndex + '[THINK]' . length ;
127+
128+ return {
129+ thinking : content . substring ( thinkingStart ) ,
130+ remainingContent : content . substring ( 0 , bracketStartIndex )
131+ } ;
132+ }
133+ } else {
134+ return { thinking : null , remainingContent : content } ;
87135 }
88136
89- // Complete thinking block found
90137 const parsed = parseThinkingContent ( content ) ;
138+
91139 return {
92140 thinking : parsed . thinking ,
93141 remainingContent : parsed . cleanContent
0 commit comments