Skip to content

Commit 68c3cbe

Browse files
authored
Merge pull request #1719 from dgageot/openapi-2
Add openapi built-in toolset type
2 parents 206db7e + 3cd519b commit 68c3cbe

File tree

10 files changed

+1036
-12
lines changed

10 files changed

+1036
-12
lines changed

cagent-schema.json

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,8 @@
679679
"api",
680680
"a2a",
681681
"lsp",
682-
"user_prompt"
682+
"user_prompt",
683+
"openapi"
683684
]
684685
},
685686
"instruction": {
@@ -791,9 +792,16 @@
791792
},
792793
"url": {
793794
"type": "string",
794-
"description": "URL for the a2a tool",
795+
"description": "URL for the a2a or openapi tool",
795796
"format": "uri"
796797
},
798+
"headers": {
799+
"type": "object",
800+
"description": "HTTP headers for API requests (supports ${env.VAR} interpolation)",
801+
"additionalProperties": {
802+
"type": "string"
803+
}
804+
},
797805
"name": {
798806
"type": "string",
799807
"description": "Name for the a2a tool"
@@ -903,6 +911,22 @@
903911
]
904912
}
905913
]
914+
},
915+
{
916+
"allOf": [
917+
{
918+
"properties": {
919+
"type": {
920+
"const": "openapi"
921+
}
922+
}
923+
},
924+
{
925+
"required": [
926+
"url"
927+
]
928+
}
929+
]
906930
}
907931
]
908932
},

examples/openapi-petstore.yaml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env cagent run
2+
3+
# OpenAPI Toolset Example
4+
#
5+
# This agent uses the "openapi" toolset type to automatically generate
6+
# tools from a public OpenAPI specification (the Pet Store API).
7+
#
8+
# Each endpoint in the spec becomes a callable tool that the agent can use.
9+
#
10+
# Usage:
11+
# cagent run examples/openapi-petstore.yaml
12+
13+
agents:
14+
root:
15+
model: openai/gpt-4o
16+
description: Pet Store assistant powered by OpenAPI tools
17+
instruction: |
18+
You are a helpful Pet Store assistant.
19+
You have access to the Pet Store API and can help users manage pets.
20+
21+
Available actions:
22+
- List pets with optional filtering
23+
- Create new pets
24+
- Look up pets by ID
25+
26+
Always confirm with the user before creating or modifying data.
27+
welcome_message: |
28+
🐾 Welcome to the Pet Store!
29+
30+
I can help you manage pets using the Pet Store API. Try asking me to:
31+
- List all available pets
32+
- Add a new pet
33+
- Look up a specific pet
34+
35+
What would you like to do?
36+
toolsets:
37+
- type: openapi
38+
url: https://petstore3.swagger.io/api/v3/openapi.json
39+
# Optional: pass custom headers to every HTTP request
40+
# headers:
41+
# Authorization: "Bearer ${env.API_TOKEN}"
42+
# X-Custom-Header: "my-value"

go.mod

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ require (
3333
github.com/dop251/goja v0.0.0-20260106131823-651366fbe6e3
3434
github.com/fatih/color v1.18.0
3535
github.com/fsnotify/fsnotify v1.9.0
36+
github.com/getkin/kin-openapi v0.132.0
3637
github.com/go-git/go-git/v5 v5.16.5
3738
github.com/goccy/go-yaml v1.19.2
3839
github.com/golang-jwt/jwt/v5 v5.3.1
@@ -136,6 +137,8 @@ require (
136137
github.com/go-git/go-billy/v5 v5.6.2 // indirect
137138
github.com/go-logr/logr v1.4.3 // indirect
138139
github.com/go-logr/stdr v1.2.2 // indirect
140+
github.com/go-openapi/jsonpointer v0.21.0 // indirect
141+
github.com/go-openapi/swag v0.23.0 // indirect
139142
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
140143
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
141144
github.com/golang/snappy v0.0.4 // indirect
@@ -149,6 +152,7 @@ require (
149152
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect
150153
github.com/inconshreveable/mousetrap v1.1.0 // indirect
151154
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
155+
github.com/josharian/intern v1.0.0 // indirect
152156
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede // indirect
153157
github.com/kevinburke/ssh_config v1.2.0 // indirect
154158
github.com/klauspost/compress v1.18.1 // indirect
@@ -158,11 +162,15 @@ require (
158162
github.com/mattn/go-colorable v0.1.14 // indirect
159163
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
160164
github.com/mitchellh/go-homedir v1.1.0 // indirect
165+
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
161166
github.com/mschoch/smat v0.2.0 // indirect
162167
github.com/muesli/cancelreader v0.2.2 // indirect
163168
github.com/ncruces/go-strftime v1.0.0 // indirect
169+
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
170+
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
164171
github.com/opencontainers/go-digest v1.0.0 // indirect
165172
github.com/opencontainers/image-spec v1.1.1 // indirect
173+
github.com/perimeterx/marshmallow v1.1.5 // indirect
166174
github.com/pjbgf/sha1cd v0.3.2 // indirect
167175
github.com/pmezard/go-difflib v1.0.0 // indirect
168176
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect

go.sum

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
190190
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
191191
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
192192
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
193+
github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=
194+
github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
193195
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
194196
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
195197
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
@@ -205,8 +207,14 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
205207
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
206208
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
207209
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
210+
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
211+
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
212+
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
213+
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
208214
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
209215
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
216+
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
217+
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
210218
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
211219
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
212220
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
@@ -253,6 +261,7 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
253261
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
254262
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
255263
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
264+
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
256265
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
257266
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede h1:YrgBGwxMRK0Vq0WSCWFaZUnTsrA/PZE/xs1QZh+/edg=
258267
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -293,6 +302,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
293302
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
294303
github.com/modelcontextprotocol/go-sdk v1.3.0 h1:gMfZkv3DzQF5q/DcQePo5rahEY+sguyPfXDfNBcT0Zs=
295304
github.com/modelcontextprotocol/go-sdk v1.3.0/go.mod h1:AnQ//Qc6+4nIyyrB4cxBU7UW9VibK4iOZBeyP/rF1IE=
305+
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
306+
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
296307
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
297308
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
298309
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
@@ -301,6 +312,10 @@ github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0
301312
github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=
302313
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
303314
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
315+
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
316+
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
317+
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
318+
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
304319
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
305320
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
306321
github.com/openai/openai-go/v3 v3.21.0 h1:3GpIR/W4q/v1uUOVuK3zYtQiF3DnRrZag/sxbtvEdtc=
@@ -309,6 +324,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
309324
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
310325
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
311326
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
327+
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
328+
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
312329
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
313330
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
314331
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -363,6 +380,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
363380
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
364381
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
365382
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
383+
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
384+
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
366385
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
367386
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
368387
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=

pkg/config/latest/types.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -420,9 +420,10 @@ type Toolset struct {
420420
Remote Remote `json:"remote"`
421421
Config any `json:"config,omitempty"`
422422

423-
// For the `a2a` tool
424-
Name string `json:"name,omitempty"`
425-
URL string `json:"url,omitempty"`
423+
// For the `a2a` and `openapi` tools
424+
Name string `json:"name,omitempty"`
425+
URL string `json:"url,omitempty"`
426+
Headers map[string]string `json:"headers,omitempty"`
426427

427428
// For `shell`, `script`, `mcp` or `lsp` tools
428429
Env map[string]string `json:"env,omitempty"`

pkg/config/latest/validate.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,16 @@ func (t *Toolset) validate() error {
9292
return errors.New("remote can only be used with type 'mcp'")
9393
}
9494
if (len(t.Remote.Headers) > 0) && (t.Type != "mcp" && t.Type != "a2a") {
95-
return errors.New("headers can only be used with type 'mcp' or 'a2a'")
95+
return errors.New("remote headers can only be used with type 'mcp' or 'a2a'")
96+
}
97+
if len(t.Headers) > 0 && t.Type != "openapi" && t.Type != "a2a" {
98+
return errors.New("headers can only be used with type 'openapi' or 'a2a'")
9699
}
97100
if t.Config != nil && t.Type != "mcp" {
98101
return errors.New("config can only be used with type 'mcp'")
99102
}
100-
if t.URL != "" && t.Type != "a2a" {
101-
return errors.New("url can only be used with type 'a2a'")
103+
if t.URL != "" && t.Type != "a2a" && t.Type != "openapi" {
104+
return errors.New("url can only be used with type 'a2a' or 'openapi'")
102105
}
103106
if t.Name != "" && (t.Type != "mcp" && t.Type != "a2a") {
104107
return errors.New("name can only be used with type 'mcp' or 'a2a'")
@@ -144,6 +147,10 @@ func (t *Toolset) validate() error {
144147
if t.Command == "" {
145148
return errors.New("lsp toolset requires a command to be set")
146149
}
150+
case "openapi":
151+
if t.URL == "" {
152+
return errors.New("openapi toolset requires a url to be set")
153+
}
147154
}
148155

149156
return nil

pkg/config/v4/types.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -420,9 +420,10 @@ type Toolset struct {
420420
Remote Remote `json:"remote"`
421421
Config any `json:"config,omitempty"`
422422

423-
// For the `a2a` tool
424-
Name string `json:"name,omitempty"`
425-
URL string `json:"url,omitempty"`
423+
// For the `a2a` and `openapi` tools
424+
Name string `json:"name,omitempty"`
425+
URL string `json:"url,omitempty"`
426+
Headers map[string]string `json:"headers,omitempty"`
426427

427428
// For `shell`, `script`, `mcp` or `lsp` tools
428429
Env map[string]string `json:"env,omitempty"`

pkg/teamloader/registry.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ func NewDefaultToolsetRegistry() *ToolsetRegistry {
7171
r.Register("a2a", createA2ATool)
7272
r.Register("lsp", createLSPTool)
7373
r.Register("user_prompt", createUserPromptTool)
74+
r.Register("openapi", createOpenAPITool)
7475
return r
7576
}
7677

@@ -294,7 +295,7 @@ func createMCPTool(ctx context.Context, toolset latest.Toolset, _ string, runCon
294295
func createA2ATool(ctx context.Context, toolset latest.Toolset, _ string, runConfig *config.RuntimeConfig) (tools.ToolSet, error) {
295296
expander := js.NewJsExpander(runConfig.EnvProvider())
296297

297-
headers := expander.ExpandMap(ctx, toolset.APIConfig.Headers)
298+
headers := expander.ExpandMap(ctx, toolset.Headers)
298299

299300
return a2a.NewToolset(toolset.Name, toolset.URL, headers), nil
300301
}
@@ -311,3 +312,12 @@ func createLSPTool(ctx context.Context, toolset latest.Toolset, _ string, runCon
311312
func createUserPromptTool(_ context.Context, _ latest.Toolset, _ string, _ *config.RuntimeConfig) (tools.ToolSet, error) {
312313
return builtin.NewUserPromptTool(), nil
313314
}
315+
316+
func createOpenAPITool(ctx context.Context, toolset latest.Toolset, _ string, runConfig *config.RuntimeConfig) (tools.ToolSet, error) {
317+
expander := js.NewJsExpander(runConfig.EnvProvider())
318+
319+
specURL := expander.Expand(ctx, toolset.URL)
320+
headers := expander.ExpandMap(ctx, toolset.Headers)
321+
322+
return builtin.NewOpenAPITool(specURL, headers), nil
323+
}

0 commit comments

Comments
 (0)