11package parser
22
33import (
4- "bufio"
54 "fmt"
65 "io"
76 "maps"
@@ -17,8 +16,10 @@ import (
1716
1817var (
1918 sepRegex = regexp .MustCompile (`^\s*---+\s*$` )
19+ endHeaderRegex = regexp .MustCompile (`^\s*===+\s*$` )
2020 strictSepRegex = regexp .MustCompile (`^---\n$` )
2121 skipRegex = regexp .MustCompile (`^![-.:*\w]+\s*$` )
22+ nameRegex = regexp .MustCompile (`^[a-z]+$` )
2223)
2324
2425func normalize (key string ) string {
@@ -56,6 +57,9 @@ func addArg(line string, tool *types.Tool) error {
5657 tool .Parameters .Arguments = & openapi3.Schema {
5758 Type : & openapi3.Types {"object" },
5859 Properties : openapi3.Schemas {},
60+ AdditionalProperties : openapi3.AdditionalProperties {
61+ Has : new (bool ),
62+ },
5963 }
6064 }
6165
@@ -74,7 +78,7 @@ func addArg(line string, tool *types.Tool) error {
7478 return nil
7579}
7680
77- func isParam (line string , tool * types.Tool ) (_ bool , err error ) {
81+ func isParam (line string , tool * types.Tool , scan * simplescanner ) (_ bool , err error ) {
7882 key , value , ok := strings .Cut (line , ":" )
7983 if ! ok {
8084 return false , nil
@@ -90,7 +94,7 @@ func isParam(line string, tool *types.Tool) (_ bool, err error) {
9094 case "globalmodel" , "globalmodelname" :
9195 tool .Parameters .GlobalModelName = value
9296 case "description" :
93- tool .Parameters .Description = value
97+ tool .Parameters .Description = scan . AddMultiline ( value )
9498 case "internalprompt" :
9599 v , err := toBool (value )
96100 if err != nil {
@@ -104,27 +108,33 @@ func isParam(line string, tool *types.Tool) (_ bool, err error) {
104108 }
105109 tool .Parameters .Chat = v
106110 case "export" , "exporttool" , "exports" , "exporttools" , "sharetool" , "sharetools" , "sharedtool" , "sharedtools" :
107- tool .Parameters .Export = append (tool .Parameters .Export , csv (value )... )
111+ tool .Parameters .Export = append (tool .Parameters .Export , csv (scan . AddMultiline ( value ) )... )
108112 case "tool" , "tools" :
109- tool .Parameters .Tools = append (tool .Parameters .Tools , csv (value )... )
113+ tool .Parameters .Tools = append (tool .Parameters .Tools , csv (scan . AddMultiline ( value ) )... )
110114 case "inputfilter" , "inputfilters" :
111- tool .Parameters .InputFilters = append (tool .Parameters .InputFilters , csv (value )... )
115+ tool .Parameters .InputFilters = append (tool .Parameters .InputFilters , csv (scan . AddMultiline ( value ) )... )
112116 case "shareinputfilter" , "shareinputfilters" , "sharedinputfilter" , "sharedinputfilters" :
113- tool .Parameters .ExportInputFilters = append (tool .Parameters .ExportInputFilters , csv (value )... )
117+ tool .Parameters .ExportInputFilters = append (tool .Parameters .ExportInputFilters , csv (scan . AddMultiline ( value ) )... )
114118 case "outputfilter" , "outputfilters" :
115- tool .Parameters .OutputFilters = append (tool .Parameters .OutputFilters , csv (value )... )
119+ tool .Parameters .OutputFilters = append (tool .Parameters .OutputFilters , csv (scan . AddMultiline ( value ) )... )
116120 case "shareoutputfilter" , "shareoutputfilters" , "sharedoutputfilter" , "sharedoutputfilters" :
117- tool .Parameters .ExportOutputFilters = append (tool .Parameters .ExportOutputFilters , csv (value )... )
121+ tool .Parameters .ExportOutputFilters = append (tool .Parameters .ExportOutputFilters , csv (scan . AddMultiline ( value ) )... )
118122 case "agent" , "agents" :
119- tool .Parameters .Agents = append (tool .Parameters .Agents , csv (value )... )
123+ tool .Parameters .Agents = append (tool .Parameters .Agents , csv (scan . AddMultiline ( value ) )... )
120124 case "globaltool" , "globaltools" :
121- tool .Parameters .GlobalTools = append (tool .Parameters .GlobalTools , csv (value )... )
125+ tool .Parameters .GlobalTools = append (tool .Parameters .GlobalTools , csv (scan . AddMultiline ( value ) )... )
122126 case "exportcontext" , "exportcontexts" , "sharecontext" , "sharecontexts" , "sharedcontext" , "sharedcontexts" :
123- tool .Parameters .ExportContext = append (tool .Parameters .ExportContext , csv (value )... )
127+ tool .Parameters .ExportContext = append (tool .Parameters .ExportContext , csv (scan . AddMultiline ( value ) )... )
124128 case "context" :
125- tool .Parameters .Context = append (tool .Parameters .Context , csv (value )... )
129+ tool .Parameters .Context = append (tool .Parameters .Context , csv (scan .AddMultiline (value ))... )
130+ case "metadata" :
131+ mkey , mvalue , _ := strings .Cut (scan .AddMultiline (value ), ":" )
132+ if tool .MetaData == nil {
133+ tool .MetaData = map [string ]string {}
134+ }
135+ tool .MetaData [strings .TrimSpace (mkey )] = strings .TrimSpace (mvalue )
126136 case "args" , "arg" , "param" , "params" , "parameters" , "parameter" :
127- if err := addArg (value , tool ); err != nil {
137+ if err := addArg (scan . AddMultiline ( value ) , tool ); err != nil {
128138 return false , err
129139 }
130140 case "maxtoken" , "maxtokens" :
@@ -149,13 +159,13 @@ func isParam(line string, tool *types.Tool) (_ bool, err error) {
149159 return false , err
150160 }
151161 case "credentials" , "creds" , "credential" , "cred" :
152- tool .Parameters .Credentials = append (tool .Parameters .Credentials , value )
162+ tool .Parameters .Credentials = append (tool .Parameters .Credentials , csv ( scan . AddMultiline ( value )) ... )
153163 case "sharecredentials" , "sharecreds" , "sharecredential" , "sharecred" , "sharedcredentials" , "sharedcreds" , "sharedcredential" , "sharedcred" :
154- tool .Parameters .ExportCredentials = append (tool .Parameters .ExportCredentials , value )
164+ tool .Parameters .ExportCredentials = append (tool .Parameters .ExportCredentials , scan . AddMultiline ( value ) )
155165 case "type" :
156166 tool .Type = types .ToolType (strings .ToLower (value ))
157167 default :
158- return false , nil
168+ return nameRegex . MatchString ( key ) , nil
159169 }
160170
161171 return true , nil
@@ -206,6 +216,7 @@ func (c *context) finish(tools *[]Node) {
206216 len (c .tool .ExportInputFilters ) > 0 ||
207217 len (c .tool .ExportOutputFilters ) > 0 ||
208218 len (c .tool .Agents ) > 0 ||
219+ len (c .tool .ExportCredentials ) > 0 ||
209220 c .tool .Chat {
210221 * tools = append (* tools , Node {
211222 ToolNode : & ToolNode {
@@ -391,7 +402,10 @@ func assignMetadata(nodes []Node) (result []Node) {
391402
392403 for _ , node := range nodes {
393404 if node .ToolNode != nil {
394- node .ToolNode .Tool .MetaData = metadata [node .ToolNode .Tool .Name ]
405+ if node .ToolNode .Tool .MetaData == nil {
406+ node .ToolNode .Tool .MetaData = map [string ]string {}
407+ }
408+ maps .Copy (node .ToolNode .Tool .MetaData , metadata [node .ToolNode .Tool .Name ])
395409 for wildcard := range metadata {
396410 if strings .Contains (wildcard , "*" ) {
397411 if m , err := path .Match (wildcard , node .ToolNode .Tool .Name ); m && err == nil {
@@ -433,15 +447,64 @@ func isGPTScriptHashBang(line string) bool {
433447 return false
434448}
435449
436- func parse (input io.Reader ) ([]Node , error ) {
437- scan := bufio .NewScanner (input )
450+ type simplescanner struct {
451+ lines []string
452+ }
453+
454+ func newSimpleScanner (data []byte ) * simplescanner {
455+ if len (data ) == 0 {
456+ return & simplescanner {}
457+ }
458+ lines := strings .Split (string (data ), "\n " )
459+ return & simplescanner {
460+ lines : append ([]string {"" }, lines ... ),
461+ }
462+ }
463+
464+ func (s * simplescanner ) AddMultiline (current string ) string {
465+ result := current
466+ for {
467+ if len (s .lines ) < 2 || len (s .lines [1 ]) == 0 {
468+ return result
469+ }
470+ if strings .HasPrefix (s .lines [1 ], " " ) || strings .HasPrefix (s .lines [1 ], "\t " ) {
471+ result += " " + s .lines [1 ]
472+ s .lines = s .lines [1 :]
473+ } else {
474+ return result
475+ }
476+ }
477+ }
438478
479+ func (s * simplescanner ) Text () string {
480+ if len (s .lines ) == 0 {
481+ return ""
482+ }
483+ return s .lines [0 ]
484+ }
485+
486+ func (s * simplescanner ) Scan () bool {
487+ if len (s .lines ) == 0 {
488+ return false
489+ }
490+ s .lines = s .lines [1 :]
491+ return true
492+ }
493+
494+ func parse (input io.Reader ) ([]Node , error ) {
439495 var (
440496 tools []Node
441497 context context
442498 lineNo int
443499 )
444500
501+ data , err := io .ReadAll (input )
502+ if err != nil {
503+ return nil , err
504+ }
505+
506+ scan := newSimpleScanner (data )
507+
445508 for scan .Scan () {
446509 lineNo ++
447510 if context .tool .Source .LineNo == 0 {
@@ -488,11 +551,15 @@ func parse(input io.Reader) ([]Node, error) {
488551 }
489552
490553 // Look for params
491- if isParam , err := isParam (line , & context .tool ); err != nil {
554+ if isParam , err := isParam (line , & context .tool , scan ); err != nil {
492555 return nil , NewErrLine ("" , lineNo , err )
493556 } else if isParam {
494557 context .seenParam = true
495558 continue
559+ } else if endHeaderRegex .MatchString (line ) {
560+ // force the end of the header and don't include the current line in the header
561+ context .inBody = true
562+ continue
496563 }
497564 }
498565
0 commit comments