@@ -482,6 +482,88 @@ export class FunctionStyleToolAdapter extends ToolFormatAdapter {
482482 }
483483}
484484
485+ /**
486+ * Adapter for bare tool call format (no wrapping tags)
487+ * Format: tool_name {"param": "value"} or tool_name {json}
488+ *
489+ * This is what models often produce naturally:
490+ * - code/search {"query": "memory clustering", "path": "./src/"}
491+ * - code/tree {"path": "./workers/"}
492+ */
493+ export class BareToolCallAdapter extends ToolFormatAdapter {
494+ readonly formatName = 'bare-tool-call' ;
495+
496+ // Known tool prefixes to identify tool calls
497+ private static TOOL_PREFIXES = [
498+ 'code/' , 'data/' , 'collaboration/' , 'ai/' , 'voice/' , 'search/' ,
499+ 'workspace/' , 'file/' , 'interface/' , 'genome/' , 'adapter/' ,
500+ 'persona/' , 'runtime/' , 'session/' , 'user/' , 'logs/' , 'media/'
501+ ] ;
502+
503+ formatToolsForPrompt ( tools : ToolDefinition [ ] ) : string {
504+ return '' ; // Parsing only
505+ }
506+
507+ formatResultsForContext ( results : Array < { toolName : string ; success : boolean ; content ?: string ; error ?: string } > ) : string {
508+ return results . map ( r => {
509+ if ( r . success && r . content ) {
510+ return `Tool ${ r . toolName } succeeded:\n${ r . content } ` ;
511+ } else {
512+ return `Tool ${ r . toolName } failed: ${ r . error || 'Unknown error' } ` ;
513+ }
514+ } ) . join ( '\n\n' ) ;
515+ }
516+
517+ matches ( text : string ) : ToolCallMatch [ ] {
518+ const matches : ToolCallMatch [ ] = [ ] ;
519+
520+ // Pattern: tool/name {json} or tool/name {"key": "value"}
521+ // Must start with known prefix to avoid false positives
522+ const prefixPattern = BareToolCallAdapter . TOOL_PREFIXES . map ( p => p . replace ( '/' , '\\/' ) ) . join ( '|' ) ;
523+ const regex = new RegExp ( `((?:${ prefixPattern } )[a-zA-Z0-9/_-]+)\\s*(\\{[^{}]*(?:\\{[^{}]*\\}[^{}]*)*\\})` , 'g' ) ;
524+
525+ let match : RegExpExecArray | null ;
526+ while ( ( match = regex . exec ( text ) ) !== null ) {
527+ matches . push ( {
528+ fullMatch : match [ 0 ] ,
529+ startIndex : match . index ,
530+ endIndex : regex . lastIndex
531+ } ) ;
532+ }
533+
534+ return matches ;
535+ }
536+
537+ parse ( match : ToolCallMatch ) : ToolCall | null {
538+ // Extract tool name and JSON
539+ const prefixPattern = BareToolCallAdapter . TOOL_PREFIXES . map ( p => p . replace ( '/' , '\\/' ) ) . join ( '|' ) ;
540+ const parseRegex = new RegExp ( `((?:${ prefixPattern } )[a-zA-Z0-9/_-]+)\\s*(\\{.+\\})` , 's' ) ;
541+ const parsed = match . fullMatch . match ( parseRegex ) ;
542+
543+ if ( ! parsed ) return null ;
544+
545+ const toolName = parsed [ 1 ] . trim ( ) ;
546+ const jsonStr = parsed [ 2 ] . trim ( ) ;
547+ const parameters : Record < string , string > = { } ;
548+
549+ try {
550+ const parsedJson = JSON . parse ( jsonStr ) ;
551+ for ( const [ key , value ] of Object . entries ( parsedJson ) ) {
552+ parameters [ key ] = typeof value === 'string' ? value : JSON . stringify ( value ) ;
553+ }
554+ } catch {
555+ // Fallback: try to extract key-value pairs
556+ const kvRegex = / " ( [ ^ " ] + ) " : \s * " ( [ ^ " ] * ) " / g;
557+ let kvMatch ;
558+ while ( ( kvMatch = kvRegex . exec ( jsonStr ) ) !== null ) {
559+ parameters [ kvMatch [ 1 ] ] = kvMatch [ 2 ] ;
560+ }
561+ }
562+
563+ return { toolName, parameters } ;
564+ }
565+ }
566+
485567/**
486568 * Registry of all supported tool format adapters
487569 * Add new adapters here to support additional formats
@@ -492,6 +574,7 @@ export function getToolFormatAdapters(): ToolFormatAdapter[] {
492574 return [
493575 new AnthropicStyleToolAdapter ( ) , // Primary/default format
494576 new FunctionStyleToolAdapter ( ) , // OpenAI/Groq/Together function style
577+ new BareToolCallAdapter ( ) , // Bare tool_name {json} format
495578 new MarkdownToolAdapter ( ) , // Local model backtick format
496579 new OldStyleToolAdapter ( ) // Legacy XML support
497580 ] ;
0 commit comments