diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 829f3d7..47b13f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,18 +1,51 @@ name: CI on: push: - branches-ignore: - - 'generated' - - 'codegen/**' - - 'integrated/**' - - 'stl-preview-head/**' - - 'stl-preview-base/**' + branches: + - '**' + - '!integrated/**' + - '!stl-preview-head/**' + - '!stl-preview-base/**' + - '!generated' + - '!codegen/**' + - 'codegen/stl/**' pull_request: branches-ignore: - 'stl-preview-head/**' - 'stl-preview-base/**' jobs: + build: + timeout-minutes: 10 + name: build + permissions: + contents: read + id-token: write + runs-on: ${{ github.repository == 'stainless-sdks/arcade-engine-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: |- + github.repository == 'stainless-sdks/arcade-engine-go' && + (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Get GitHub OIDC Token + if: |- + github.repository == 'stainless-sdks/arcade-engine-go' && + !startsWith(github.ref, 'refs/heads/stl/') + id: github-oidc + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: core.setOutput('github_token', await core.getIDToken()); + + - name: Upload tarball + if: |- + github.repository == 'stainless-sdks/arcade-engine-go' && + !startsWith(github.ref, 'refs/heads/stl/') + env: + URL: https://pkg.stainless.com/s + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + run: ./scripts/utils/upload-artifact.sh lint: timeout-minutes: 10 name: lint @@ -20,10 +53,10 @@ jobs: if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup go - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version-file: ./go.mod @@ -35,10 +68,10 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/arcade-engine-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup go - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version-file: ./go.mod diff --git a/.gitignore b/.gitignore index c6d0501..8554aff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .prism.log +.stdy.log codegen.log Brewfile.lock.json .idea/ diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c373724..46b9b6b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.8" + ".": "0.1.0-alpha.9" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index d521f65..5d78909 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 29 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-0a15ddd7e03addf08468ff36ac294458f86a3a990277a71870e4bc293635bef9.yml -openapi_spec_hash: 8640228f8a86e5dc464dfa2c8205a2a7 -config_hash: 70cdb57c982c578d1961657c07b8b397 +configured_endpoints: 30 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai/arcade-engine-eaf2f7aadf03307d7e9af6e581241cc861df65beeebe8c3e7c069d400b9a40a1.yml +openapi_spec_hash: 4155f85e560559c3446f4742552b25df +config_hash: 2d4163acdeacd75903f978cd79c35d14 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2621d57..0ec8b54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,86 @@ # Changelog +## 0.1.0-alpha.9 (2026-05-26) + +Full Changelog: [v0.1.0-alpha.8...v0.1.0-alpha.9](https://github.com/ArcadeAI/arcade-go/compare/v0.1.0-alpha.8...v0.1.0-alpha.9) + +### Features + +* **api:** api update ([03a11ee](https://github.com/ArcadeAI/arcade-go/commit/03a11ee06674cd5285f049380ca996e83518f26f)) +* **api:** api update ([15af298](https://github.com/ArcadeAI/arcade-go/commit/15af2985b096cacf95d8341690a0f0695503f1f7)) +* **api:** api update ([260cc3c](https://github.com/ArcadeAI/arcade-go/commit/260cc3ce191cb400eca7d346626a49130cb31d53)) +* **api:** api update ([23431c0](https://github.com/ArcadeAI/arcade-go/commit/23431c015087f68e83143eb3f4793932309564d8)) +* **api:** api update ([0de537a](https://github.com/ArcadeAI/arcade-go/commit/0de537ac10f2926566b8a62c42b874a0e8bff817)) +* **api:** api update ([e8333c4](https://github.com/ArcadeAI/arcade-go/commit/e8333c47cae6a2e6c218c705196796691fabbfc5)) +* **api:** api update ([4c00a3b](https://github.com/ArcadeAI/arcade-go/commit/4c00a3bfa62910224a49680ab7a2e4919e93e6e7)) +* **api:** api update ([8e0a60b](https://github.com/ArcadeAI/arcade-go/commit/8e0a60b92a5d9aaf32d3b1fd3c008921972ccec8)) +* **api:** api update ([e7ec3a4](https://github.com/ArcadeAI/arcade-go/commit/e7ec3a489005741fb4fe27aed0223e479aaf15d6)) +* **api:** api update ([ef6dba7](https://github.com/ArcadeAI/arcade-go/commit/ef6dba7dcc75ec8a355da12108145dc966564949)) +* **api:** api update ([e295d78](https://github.com/ArcadeAI/arcade-go/commit/e295d78443c56c5d146c1ce425cc0b94aa986abd)) +* **api:** api update ([f3cafbb](https://github.com/ArcadeAI/arcade-go/commit/f3cafbbda12389ddac755ad764cdfca589fed640)) +* **api:** api update ([426bd50](https://github.com/ArcadeAI/arcade-go/commit/426bd5094794f1fe5cf17928871bdbf7c8372cbc)) +* **api:** api update ([31b05fe](https://github.com/ArcadeAI/arcade-go/commit/31b05fe785b727d1fce4a403f6995f354476a3ab)) +* **api:** api update ([dba7b85](https://github.com/ArcadeAI/arcade-go/commit/dba7b8599c6b63c207634e3ac9a8250abbd8c478)) +* **api:** api update ([96bb51d](https://github.com/ArcadeAI/arcade-go/commit/96bb51dc393da8eda932aebcb8817e15c6f0fd3c)) +* **api:** api update ([8a8ad69](https://github.com/ArcadeAI/arcade-go/commit/8a8ad6959f8ae3aed9085661c6efd7a19713ea41)) +* **api:** api update ([225468e](https://github.com/ArcadeAI/arcade-go/commit/225468eb94ab97ea95c360939964eef2c9f15142)) +* **go:** add default http client with timeout ([9225e7d](https://github.com/ArcadeAI/arcade-go/commit/9225e7dcc923c972329f660f36d0ead9907ea8b2)) +* **internal:** support comma format in multipart form encoding ([da159cd](https://github.com/ArcadeAI/arcade-go/commit/da159cdf03283224fee066a351aaa194907b096e)) +* support setting headers via env ([68db5fd](https://github.com/ArcadeAI/arcade-go/commit/68db5fd59a067429a2231efb6c52695d895a3338)) + + +### Bug Fixes + +* allow canceling a request while it is waiting to retry ([5093f28](https://github.com/ArcadeAI/arcade-go/commit/5093f2889531dc14af7be689f2af8baba011f606)) +* **client:** correctly specify Accept header with */* instead of empty ([b4971e9](https://github.com/ArcadeAI/arcade-go/commit/b4971e9ac4114e03f0b91628a5e5bea5055d4d90)) +* **docs:** add missing pointer prefix to api.md return types ([11168f2](https://github.com/ArcadeAI/arcade-go/commit/11168f2105fd658a16d9378d56dc283925e31412)) +* **go:** avoid panic when http.DefaultTransport is wrapped ([6b0275e](https://github.com/ArcadeAI/arcade-go/commit/6b0275e49df880eb44238c3286f6166ba8a6b072)) +* **mcp:** correct code tool API endpoint ([cdd7fa0](https://github.com/ArcadeAI/arcade-go/commit/cdd7fa02a5ca03e198cad5a969e7e2951e6fb372)) +* prevent duplicate ? in query params ([f5f5733](https://github.com/ArcadeAI/arcade-go/commit/f5f57333f5ad6fe24346182fbc6004e3da558b0c)) +* rename param to avoid collision ([31f124a](https://github.com/ArcadeAI/arcade-go/commit/31f124a0dd547c82e1655a34e30be9bbed9ed06f)) + + +### Chores + +* avoid embedding reflect.Type for dead code elimination ([dd5d356](https://github.com/ArcadeAI/arcade-go/commit/dd5d3561efc4f2857ebfdf5e514d28384f2a9e54)) +* **ci:** skip lint on metadata-only changes ([9a5a06a](https://github.com/ArcadeAI/arcade-go/commit/9a5a06a01559aacd2c9fa8ca596312ab724c52ae)) +* **ci:** skip uploading artifacts on stainless-internal branches ([70b177c](https://github.com/ArcadeAI/arcade-go/commit/70b177ccc9ab49e4da4eaed477106fa56d14bbc9)) +* **ci:** support opting out of skipping builds on metadata-only commits ([836da69](https://github.com/ArcadeAI/arcade-go/commit/836da69c65e6c66f3f199da76c9e7486c1cf8565)) +* elide duplicate aliases ([37f1785](https://github.com/ArcadeAI/arcade-go/commit/37f1785677e7707685049d97ea3fc88f6955cd45)) +* **internal:** codegen related update ([49341b2](https://github.com/ArcadeAI/arcade-go/commit/49341b277a9d0824d09c0e0eb5fc3b87ff104c20)) +* **internal:** codegen related update ([d308faf](https://github.com/ArcadeAI/arcade-go/commit/d308fafcd3b08787165aa32d4b89302368d85396)) +* **internal:** codegen related update ([d2a63a5](https://github.com/ArcadeAI/arcade-go/commit/d2a63a5c1426deb8172d2f98a3976e37b10a8d1d)) +* **internal:** codegen related update ([263a9c8](https://github.com/ArcadeAI/arcade-go/commit/263a9c8e76c9920b0b7984701e9498da2cee6398)) +* **internal:** minor cleanup ([6dfc676](https://github.com/ArcadeAI/arcade-go/commit/6dfc676d0446ff839d9471a45b27c18a66c609cf)) +* **internal:** more robust bootstrap script ([a1a0ef8](https://github.com/ArcadeAI/arcade-go/commit/a1a0ef87883a08269eb0481b0b187aef335bd50f)) +* **internal:** move custom custom `json` tags to `api` ([cedae74](https://github.com/ArcadeAI/arcade-go/commit/cedae746b544a425461567895a612b8a3f6753fc)) +* **internal:** tweak CI branches ([107a1dd](https://github.com/ArcadeAI/arcade-go/commit/107a1dd493e9923873035a3c4b638614a02ba868)) +* **internal:** update `actions/checkout` version ([c507015](https://github.com/ArcadeAI/arcade-go/commit/c5070155e8a43faf42a500d32d97e2a569c3eea0)) +* **internal:** update gitignore ([bcbbcd2](https://github.com/ArcadeAI/arcade-go/commit/bcbbcd24d3603f0317aaf92cbbbc14fade166423)) +* **internal:** use explicit returns ([f107ebe](https://github.com/ArcadeAI/arcade-go/commit/f107ebec55f86a2b82e8678b83f6856ac3c39863)) +* **internal:** use explicit returns in more places ([97ffe54](https://github.com/ArcadeAI/arcade-go/commit/97ffe542f9a6e82f02843e1f2a14871c398f644a)) +* redact api-key headers in debug logs ([f550248](https://github.com/ArcadeAI/arcade-go/commit/f550248378a4c5517364db6b82e4caf82aee828d)) +* remove unnecessary error check for url parsing ([b976417](https://github.com/ArcadeAI/arcade-go/commit/b976417506dea1a4e520634e50e09ad5332114e5)) +* **test:** do not count install time for mock server timeout ([7defc79](https://github.com/ArcadeAI/arcade-go/commit/7defc79a6f60f1ce26d28c238702d011aa4ba752)) +* **tests:** bump steady to v0.19.4 ([171aec7](https://github.com/ArcadeAI/arcade-go/commit/171aec78e38cbad7460d139cf2ef3e6ccec084e3)) +* **tests:** bump steady to v0.19.5 ([3c43671](https://github.com/ArcadeAI/arcade-go/commit/3c436718511052fece49d888393f008d2fca278b)) +* **tests:** bump steady to v0.19.6 ([ffeb63b](https://github.com/ArcadeAI/arcade-go/commit/ffeb63b3d7521a1cb3b70dd1418af9aa18f249d6)) +* **tests:** bump steady to v0.19.7 ([ad2f360](https://github.com/ArcadeAI/arcade-go/commit/ad2f36099721a32b1a2793096345d4683b0ba83c)) +* **tests:** bump steady to v0.20.1 ([03c535a](https://github.com/ArcadeAI/arcade-go/commit/03c535a788ee4c16e6581e07849c78a19fd54fe1)) +* **tests:** bump steady to v0.20.2 ([00aa1f0](https://github.com/ArcadeAI/arcade-go/commit/00aa1f05777151b994726849614781989b3ae271)) +* **tests:** bump steady to v0.22.1 ([cc37188](https://github.com/ArcadeAI/arcade-go/commit/cc37188618339eb535cdcec8275c3564797b9aff)) +* update mock server docs ([78e159d](https://github.com/ArcadeAI/arcade-go/commit/78e159d152c1a41ce41f28c28902fa63913ba87d)) + + +### Documentation + +* add more examples ([c8e95c7](https://github.com/ArcadeAI/arcade-go/commit/c8e95c7814031cf786e5e6d09e4bf8b51a48063a)) + + +### Refactors + +* **tests:** switch from prism to steady ([a0e535e](https://github.com/ArcadeAI/arcade-go/commit/a0e535e7bc517918a803d20c9ffe7cf802485f99)) + ## 0.1.0-alpha.8 (2025-11-07) Full Changelog: [v0.1.0-alpha.7...v0.1.0-alpha.8](https://github.com/ArcadeAI/arcade-go/compare/v0.1.0-alpha.7...v0.1.0-alpha.8) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 99b994b..e5d07f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,11 +46,10 @@ $ go mod edit -replace github.com/ArcadeAI/arcade-go=/path/to/arcade-go ## Running tests -Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. +Most tests require you to [set up a mock server](https://github.com/dgellow/steady) against the OpenAPI spec to run the tests. ```sh -# you will need npm installed -$ npx prism mock path/to/your/openapi.yml +$ ./scripts/mock ``` ```sh diff --git a/LICENSE b/LICENSE index 586b176..fed1c6e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2025 Arcade +Copyright 2026 Arcade Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index 503a2ed..78d6ec2 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Or to pin the version: ```sh -go get -u 'github.com/ArcadeAI/arcade-go@v0.1.0-alpha.8' +go get -u 'github.com/ArcadeAI/arcade-go@v0.1.0-alpha.9' ``` @@ -165,9 +165,34 @@ This library provides some conveniences for working with paginated list endpoint You can use `.ListAutoPaging()` methods to iterate through items across all pages: +```go +iter := client.Admin.UserConnections.ListAutoPaging(context.TODO(), arcadego.AdminUserConnectionListParams{}) +// Automatically fetches more pages as needed. +for iter.Next() { + userConnectionResponse := iter.Current() + fmt.Printf("%+v\n", userConnectionResponse) +} +if err := iter.Err(); err != nil { + panic(err.Error()) +} +``` + Or you can use simple `.List()` methods to fetch a single page and receive a standard response object with additional helper methods like `.GetNextPage()`, e.g.: +```go +page, err := client.Admin.UserConnections.List(context.TODO(), arcadego.AdminUserConnectionListParams{}) +for page != nil { + for _, userConnection := range page.Items { + fmt.Printf("%+v\n", userConnection) + } + page, err = page.GetNextPage() +} +if err != nil { + panic(err.Error()) +} +``` + ### Errors When the API returns a non-success status code, we return an error with type diff --git a/adminauthprovider.go b/adminauthprovider.go index 4b1afa9..6185bb8 100644 --- a/adminauthprovider.go +++ b/adminauthprovider.go @@ -39,7 +39,7 @@ func (r *AdminAuthProviderService) New(ctx context.Context, body AdminAuthProvid opts = slices.Concat(r.Options, opts) path := "v1/admin/auth_providers" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + return res, err } // List a page of auth providers that are available to the caller @@ -47,7 +47,7 @@ func (r *AdminAuthProviderService) List(ctx context.Context, opts ...option.Requ opts = slices.Concat(r.Options, opts) path := "v1/admin/auth_providers" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return + return res, err } // Delete a specific auth provider @@ -55,11 +55,11 @@ func (r *AdminAuthProviderService) Delete(ctx context.Context, id string, opts . opts = slices.Concat(r.Options, opts) if id == "" { err = errors.New("missing required id parameter") - return + return nil, err } path := fmt.Sprintf("v1/admin/auth_providers/%s", id) err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, &res, opts...) - return + return res, err } // Get the details of a specific auth provider @@ -67,11 +67,11 @@ func (r *AdminAuthProviderService) Get(ctx context.Context, id string, opts ...o opts = slices.Concat(r.Options, opts) if id == "" { err = errors.New("missing required id parameter") - return + return nil, err } path := fmt.Sprintf("v1/admin/auth_providers/%s", id) err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return + return res, err } // Patch an existing auth provider @@ -79,15 +79,15 @@ func (r *AdminAuthProviderService) Patch(ctx context.Context, id string, body Ad opts = slices.Concat(r.Options, opts) if id == "" { err = errors.New("missing required id parameter") - return + return nil, err } path := fmt.Sprintf("v1/admin/auth_providers/%s", id) err = requestconfig.ExecuteNewRequest(ctx, http.MethodPatch, path, body, &res, opts...) - return + return res, err } type AuthProviderCreateRequestParam struct { - ID param.Field[string] `json:"id,required"` + ID param.Field[string] `json:"id" api:"required"` Description param.Field[string] `json:"description"` // The unique external ID for the auth provider ExternalID param.Field[string] `json:"external_id"` @@ -102,7 +102,7 @@ func (r AuthProviderCreateRequestParam) MarshalJSON() (data []byte, err error) { } type AuthProviderCreateRequestOauth2Param struct { - ClientID param.Field[string] `json:"client_id,required"` + ClientID param.Field[string] `json:"client_id" api:"required"` AuthorizeRequest param.Field[AuthProviderCreateRequestOauth2AuthorizeRequestParam] `json:"authorize_request"` ClientSecret param.Field[string] `json:"client_secret"` Pkce param.Field[AuthProviderCreateRequestOauth2PkceParam] `json:"pkce"` @@ -118,7 +118,7 @@ func (r AuthProviderCreateRequestOauth2Param) MarshalJSON() (data []byte, err er } type AuthProviderCreateRequestOauth2AuthorizeRequestParam struct { - Endpoint param.Field[string] `json:"endpoint,required"` + Endpoint param.Field[string] `json:"endpoint" api:"required"` AuthHeaderValueFormat param.Field[string] `json:"auth_header_value_format"` AuthMethod param.Field[string] `json:"auth_method"` Method param.Field[string] `json:"method"` @@ -172,7 +172,7 @@ func (r AuthProviderCreateRequestOauth2PkceParam) MarshalJSON() (data []byte, er } type AuthProviderCreateRequestOauth2RefreshRequestParam struct { - Endpoint param.Field[string] `json:"endpoint,required"` + Endpoint param.Field[string] `json:"endpoint" api:"required"` AuthHeaderValueFormat param.Field[string] `json:"auth_header_value_format"` AuthMethod param.Field[string] `json:"auth_method"` Method param.Field[string] `json:"method"` @@ -232,8 +232,8 @@ func (r AuthProviderCreateRequestOauth2ScopeDelimiter) IsKnown() bool { } type AuthProviderCreateRequestOauth2TokenIntrospectionRequestParam struct { - Endpoint param.Field[string] `json:"endpoint,required"` - Triggers param.Field[AuthProviderCreateRequestOauth2TokenIntrospectionRequestTriggersParam] `json:"triggers,required"` + Endpoint param.Field[string] `json:"endpoint" api:"required"` + Triggers param.Field[AuthProviderCreateRequestOauth2TokenIntrospectionRequestTriggersParam] `json:"triggers" api:"required"` AuthHeaderValueFormat param.Field[string] `json:"auth_header_value_format"` AuthMethod param.Field[string] `json:"auth_method"` Method param.Field[string] `json:"method"` @@ -287,7 +287,7 @@ func (r AuthProviderCreateRequestOauth2TokenIntrospectionRequestResponseContentT } type AuthProviderCreateRequestOauth2TokenRequestParam struct { - Endpoint param.Field[string] `json:"endpoint,required"` + Endpoint param.Field[string] `json:"endpoint" api:"required"` AuthHeaderValueFormat param.Field[string] `json:"auth_header_value_format"` AuthMethod param.Field[string] `json:"auth_method"` Method param.Field[string] `json:"method"` @@ -332,8 +332,8 @@ func (r AuthProviderCreateRequestOauth2TokenRequestResponseContentType) IsKnown( } type AuthProviderCreateRequestOauth2UserInfoRequestParam struct { - Endpoint param.Field[string] `json:"endpoint,required"` - Triggers param.Field[AuthProviderCreateRequestOauth2UserInfoRequestTriggersParam] `json:"triggers,required"` + Endpoint param.Field[string] `json:"endpoint" api:"required"` + Triggers param.Field[AuthProviderCreateRequestOauth2UserInfoRequestTriggersParam] `json:"triggers" api:"required"` AuthHeaderValueFormat param.Field[string] `json:"auth_header_value_format"` AuthMethod param.Field[string] `json:"auth_method"` Method param.Field[string] `json:"method"` @@ -544,7 +544,6 @@ type AuthProviderResponseOauth2ClientSecret struct { Binding AuthProviderResponseOauth2ClientSecretBinding `json:"binding"` Editable bool `json:"editable"` Exists bool `json:"exists"` - Hint string `json:"hint"` Value string `json:"value"` JSON authProviderResponseOauth2ClientSecretJSON `json:"-"` } @@ -555,7 +554,6 @@ type authProviderResponseOauth2ClientSecretJSON struct { Binding apijson.Field Editable apijson.Field Exists apijson.Field - Hint apijson.Field Value apijson.Field raw string ExtraFields map[string]apijson.Field @@ -824,14 +822,15 @@ func (r AuthProviderUpdateRequestParam) MarshalJSON() (data []byte, err error) { } type AuthProviderUpdateRequestOauth2Param struct { - AuthorizeRequest param.Field[AuthProviderUpdateRequestOauth2AuthorizeRequestParam] `json:"authorize_request"` - ClientID param.Field[string] `json:"client_id"` - ClientSecret param.Field[string] `json:"client_secret"` - Pkce param.Field[AuthProviderUpdateRequestOauth2PkceParam] `json:"pkce"` - RefreshRequest param.Field[AuthProviderUpdateRequestOauth2RefreshRequestParam] `json:"refresh_request"` - ScopeDelimiter param.Field[AuthProviderUpdateRequestOauth2ScopeDelimiter] `json:"scope_delimiter"` - TokenRequest param.Field[AuthProviderUpdateRequestOauth2TokenRequestParam] `json:"token_request"` - UserInfoRequest param.Field[AuthProviderUpdateRequestOauth2UserInfoRequestParam] `json:"user_info_request"` + AuthorizeRequest param.Field[AuthProviderUpdateRequestOauth2AuthorizeRequestParam] `json:"authorize_request"` + ClientID param.Field[string] `json:"client_id"` + ClientSecret param.Field[string] `json:"client_secret"` + Pkce param.Field[AuthProviderUpdateRequestOauth2PkceParam] `json:"pkce"` + RefreshRequest param.Field[AuthProviderUpdateRequestOauth2RefreshRequestParam] `json:"refresh_request"` + ScopeDelimiter param.Field[AuthProviderUpdateRequestOauth2ScopeDelimiter] `json:"scope_delimiter"` + TokenIntrospectionRequest param.Field[AuthProviderUpdateRequestOauth2TokenIntrospectionRequestParam] `json:"token_introspection_request"` + TokenRequest param.Field[AuthProviderUpdateRequestOauth2TokenRequestParam] `json:"token_request"` + UserInfoRequest param.Field[AuthProviderUpdateRequestOauth2UserInfoRequestParam] `json:"user_info_request"` } func (r AuthProviderUpdateRequestOauth2Param) MarshalJSON() (data []byte, err error) { @@ -952,6 +951,61 @@ func (r AuthProviderUpdateRequestOauth2ScopeDelimiter) IsKnown() bool { return false } +type AuthProviderUpdateRequestOauth2TokenIntrospectionRequestParam struct { + AuthHeaderValueFormat param.Field[string] `json:"auth_header_value_format"` + AuthMethod param.Field[string] `json:"auth_method"` + Endpoint param.Field[string] `json:"endpoint"` + Method param.Field[string] `json:"method"` + Params param.Field[map[string]string] `json:"params"` + RequestContentType param.Field[AuthProviderUpdateRequestOauth2TokenIntrospectionRequestRequestContentType] `json:"request_content_type"` + ResponseContentType param.Field[AuthProviderUpdateRequestOauth2TokenIntrospectionRequestResponseContentType] `json:"response_content_type"` + ResponseMap param.Field[map[string]string] `json:"response_map"` + Triggers param.Field[AuthProviderUpdateRequestOauth2TokenIntrospectionRequestTriggersParam] `json:"triggers"` +} + +func (r AuthProviderUpdateRequestOauth2TokenIntrospectionRequestParam) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + +type AuthProviderUpdateRequestOauth2TokenIntrospectionRequestRequestContentType string + +const ( + AuthProviderUpdateRequestOauth2TokenIntrospectionRequestRequestContentTypeApplicationXWwwFormUrlencoded AuthProviderUpdateRequestOauth2TokenIntrospectionRequestRequestContentType = "application/x-www-form-urlencoded" + AuthProviderUpdateRequestOauth2TokenIntrospectionRequestRequestContentTypeApplicationJson AuthProviderUpdateRequestOauth2TokenIntrospectionRequestRequestContentType = "application/json" +) + +func (r AuthProviderUpdateRequestOauth2TokenIntrospectionRequestRequestContentType) IsKnown() bool { + switch r { + case AuthProviderUpdateRequestOauth2TokenIntrospectionRequestRequestContentTypeApplicationXWwwFormUrlencoded, AuthProviderUpdateRequestOauth2TokenIntrospectionRequestRequestContentTypeApplicationJson: + return true + } + return false +} + +type AuthProviderUpdateRequestOauth2TokenIntrospectionRequestResponseContentType string + +const ( + AuthProviderUpdateRequestOauth2TokenIntrospectionRequestResponseContentTypeApplicationXWwwFormUrlencoded AuthProviderUpdateRequestOauth2TokenIntrospectionRequestResponseContentType = "application/x-www-form-urlencoded" + AuthProviderUpdateRequestOauth2TokenIntrospectionRequestResponseContentTypeApplicationJson AuthProviderUpdateRequestOauth2TokenIntrospectionRequestResponseContentType = "application/json" +) + +func (r AuthProviderUpdateRequestOauth2TokenIntrospectionRequestResponseContentType) IsKnown() bool { + switch r { + case AuthProviderUpdateRequestOauth2TokenIntrospectionRequestResponseContentTypeApplicationXWwwFormUrlencoded, AuthProviderUpdateRequestOauth2TokenIntrospectionRequestResponseContentTypeApplicationJson: + return true + } + return false +} + +type AuthProviderUpdateRequestOauth2TokenIntrospectionRequestTriggersParam struct { + OnTokenGrant param.Field[bool] `json:"on_token_grant"` + OnTokenRefresh param.Field[bool] `json:"on_token_refresh"` +} + +func (r AuthProviderUpdateRequestOauth2TokenIntrospectionRequestTriggersParam) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + type AuthProviderUpdateRequestOauth2TokenRequestParam struct { AuthHeaderValueFormat param.Field[string] `json:"auth_header_value_format"` AuthMethod param.Field[string] `json:"auth_method"` @@ -1082,7 +1136,7 @@ func (r adminAuthProviderListResponseJSON) RawJSON() string { } type AdminAuthProviderNewParams struct { - AuthProviderCreateRequest AuthProviderCreateRequestParam `json:"auth_provider_create_request,required"` + AuthProviderCreateRequest AuthProviderCreateRequestParam `json:"auth_provider_create_request" api:"required"` } func (r AdminAuthProviderNewParams) MarshalJSON() (data []byte, err error) { @@ -1090,7 +1144,7 @@ func (r AdminAuthProviderNewParams) MarshalJSON() (data []byte, err error) { } type AdminAuthProviderPatchParams struct { - AuthProviderUpdateRequest AuthProviderUpdateRequestParam `json:"auth_provider_update_request,required"` + AuthProviderUpdateRequest AuthProviderUpdateRequestParam `json:"auth_provider_update_request" api:"required"` } func (r AdminAuthProviderPatchParams) MarshalJSON() (data []byte, err error) { diff --git a/adminauthprovider_test.go b/adminauthprovider_test.go index 0a22f4a..ac1a937 100644 --- a/adminauthprovider_test.go +++ b/adminauthprovider_test.go @@ -252,6 +252,24 @@ func TestAdminAuthProviderPatchWithOptionalParams(t *testing.T) { }), }), ScopeDelimiter: arcadego.F(arcadego.AuthProviderUpdateRequestOauth2ScopeDelimiterUnknown1), + TokenIntrospectionRequest: arcadego.F(arcadego.AuthProviderUpdateRequestOauth2TokenIntrospectionRequestParam{ + AuthHeaderValueFormat: arcadego.F("auth_header_value_format"), + AuthMethod: arcadego.F("auth_method"), + Endpoint: arcadego.F("endpoint"), + Method: arcadego.F("method"), + Params: arcadego.F(map[string]string{ + "foo": "string", + }), + RequestContentType: arcadego.F(arcadego.AuthProviderUpdateRequestOauth2TokenIntrospectionRequestRequestContentTypeApplicationXWwwFormUrlencoded), + ResponseContentType: arcadego.F(arcadego.AuthProviderUpdateRequestOauth2TokenIntrospectionRequestResponseContentTypeApplicationXWwwFormUrlencoded), + ResponseMap: arcadego.F(map[string]string{ + "foo": "string", + }), + Triggers: arcadego.F(arcadego.AuthProviderUpdateRequestOauth2TokenIntrospectionRequestTriggersParam{ + OnTokenGrant: arcadego.F(true), + OnTokenRefresh: arcadego.F(true), + }), + }), TokenRequest: arcadego.F(arcadego.AuthProviderUpdateRequestOauth2TokenRequestParam{ AuthHeaderValueFormat: arcadego.F("auth_header_value_format"), AuthMethod: arcadego.F("auth_method"), diff --git a/adminsecret.go b/adminsecret.go index 06f9a3c..d31f892 100644 --- a/adminsecret.go +++ b/adminsecret.go @@ -10,6 +10,7 @@ import ( "slices" "github.com/ArcadeAI/arcade-go/internal/apijson" + "github.com/ArcadeAI/arcade-go/internal/param" "github.com/ArcadeAI/arcade-go/internal/requestconfig" "github.com/ArcadeAI/arcade-go/option" ) @@ -33,25 +34,37 @@ func NewAdminSecretService(opts ...option.RequestOption) (r *AdminSecretService) return } +// Create or update a secret +func (r *AdminSecretService) New(ctx context.Context, secretKey string, body AdminSecretNewParams, opts ...option.RequestOption) (res *SecretResponse, err error) { + opts = slices.Concat(r.Options, opts) + if secretKey == "" { + err = errors.New("missing required secret_key parameter") + return nil, err + } + path := fmt.Sprintf("v1/admin/secrets/%s", secretKey) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) + return res, err +} + // List all secrets that are visible to the caller func (r *AdminSecretService) List(ctx context.Context, opts ...option.RequestOption) (res *AdminSecretListResponse, err error) { opts = slices.Concat(r.Options, opts) path := "v1/admin/secrets" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return + return res, err } // Delete a secret by its ID func (r *AdminSecretService) Delete(ctx context.Context, secretID string, opts ...option.RequestOption) (err error) { opts = slices.Concat(r.Options, opts) - opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...) + opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...) if secretID == "" { err = errors.New("missing required secret_id parameter") - return + return err } path := fmt.Sprintf("v1/admin/secrets/%s", secretID) err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...) - return + return err } type SecretResponse struct { @@ -59,7 +72,6 @@ type SecretResponse struct { Binding SecretResponseBinding `json:"binding"` CreatedAt string `json:"created_at"` Description string `json:"description"` - Hint string `json:"hint"` Key string `json:"key"` LastAccessedAt string `json:"last_accessed_at"` UpdatedAt string `json:"updated_at"` @@ -72,7 +84,6 @@ type secretResponseJSON struct { Binding apijson.Field CreatedAt apijson.Field Description apijson.Field - Hint apijson.Field Key apijson.Field LastAccessedAt apijson.Field UpdatedAt apijson.Field @@ -156,3 +167,12 @@ func (r *AdminSecretListResponse) UnmarshalJSON(data []byte) (err error) { func (r adminSecretListResponseJSON) RawJSON() string { return r.raw } + +type AdminSecretNewParams struct { + Value param.Field[string] `json:"value" api:"required"` + Description param.Field[string] `json:"description"` +} + +func (r AdminSecretNewParams) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} diff --git a/adminsecret_test.go b/adminsecret_test.go index 3aca95c..8b23303 100644 --- a/adminsecret_test.go +++ b/adminsecret_test.go @@ -13,6 +13,35 @@ import ( "github.com/ArcadeAI/arcade-go/option" ) +func TestAdminSecretNewWithOptionalParams(t *testing.T) { + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := arcadego.NewClient( + option.WithBaseURL(baseURL), + option.WithAPIKey("My API Key"), + ) + _, err := client.Admin.Secrets.New( + context.TODO(), + "secret_key", + arcadego.AdminSecretNewParams{ + Value: arcadego.F("value"), + Description: arcadego.F("description"), + }, + ) + if err != nil { + var apierr *arcadego.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + func TestAdminSecretList(t *testing.T) { baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { diff --git a/adminuserconnection.go b/adminuserconnection.go index 3fe52d9..f047684 100644 --- a/adminuserconnection.go +++ b/adminuserconnection.go @@ -63,14 +63,14 @@ func (r *AdminUserConnectionService) ListAutoPaging(ctx context.Context, query A // Delete a user/auth provider connection func (r *AdminUserConnectionService) Delete(ctx context.Context, id string, opts ...option.RequestOption) (err error) { opts = slices.Concat(r.Options, opts) - opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...) + opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...) if id == "" { err = errors.New("missing required id parameter") - return + return err } path := fmt.Sprintf("v1/admin/user_connections/%s", id) err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...) - return + return err } type UserConnectionResponse struct { @@ -114,42 +114,16 @@ type AdminUserConnectionListParams struct { // Page size Limit param.Field[int64] `query:"limit"` // Page offset - Offset param.Field[int64] `query:"offset"` - Provider param.Field[AdminUserConnectionListParamsProvider] `query:"provider"` - User param.Field[AdminUserConnectionListParamsUser] `query:"user"` -} - -// URLQuery serializes [AdminUserConnectionListParams]'s query parameters as -// `url.Values`. -func (r AdminUserConnectionListParams) URLQuery() (v url.Values) { - return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ - ArrayFormat: apiquery.ArrayQueryFormatComma, - NestedFormat: apiquery.NestedQueryFormatBrackets, - }) -} - -type AdminUserConnectionListParamsProvider struct { + Offset param.Field[int64] `query:"offset"` // Provider ID - ID param.Field[string] `query:"id"` -} - -// URLQuery serializes [AdminUserConnectionListParamsProvider]'s query parameters -// as `url.Values`. -func (r AdminUserConnectionListParamsProvider) URLQuery() (v url.Values) { - return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ - ArrayFormat: apiquery.ArrayQueryFormatComma, - NestedFormat: apiquery.NestedQueryFormatBrackets, - }) -} - -type AdminUserConnectionListParamsUser struct { + ProviderID param.Field[string] `query:"provider_id"` // User ID - ID param.Field[string] `query:"id"` + UserID param.Field[string] `query:"user_id"` } -// URLQuery serializes [AdminUserConnectionListParamsUser]'s query parameters as +// URLQuery serializes [AdminUserConnectionListParams]'s query parameters as // `url.Values`. -func (r AdminUserConnectionListParamsUser) URLQuery() (v url.Values) { +func (r AdminUserConnectionListParams) URLQuery() (v url.Values) { return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ ArrayFormat: apiquery.ArrayQueryFormatComma, NestedFormat: apiquery.NestedQueryFormatBrackets, diff --git a/adminuserconnection_test.go b/adminuserconnection_test.go index ef75334..66693f4 100644 --- a/adminuserconnection_test.go +++ b/adminuserconnection_test.go @@ -26,14 +26,10 @@ func TestAdminUserConnectionListWithOptionalParams(t *testing.T) { option.WithAPIKey("My API Key"), ) _, err := client.Admin.UserConnections.List(context.TODO(), arcadego.AdminUserConnectionListParams{ - Limit: arcadego.F(int64(0)), - Offset: arcadego.F(int64(0)), - Provider: arcadego.F(arcadego.AdminUserConnectionListParamsProvider{ - ID: arcadego.F("id"), - }), - User: arcadego.F(arcadego.AdminUserConnectionListParamsUser{ - ID: arcadego.F("id"), - }), + Limit: arcadego.F(int64(0)), + Offset: arcadego.F(int64(0)), + ProviderID: arcadego.F("provider_id"), + UserID: arcadego.F("user_id"), }) if err != nil { var apierr *arcadego.Error diff --git a/api.md b/api.md index 7b69123..df901c3 100644 --- a/api.md +++ b/api.md @@ -13,7 +13,7 @@ Response Types: Methods: -- client.Admin.UserConnections.List(ctx context.Context, query arcadego.AdminUserConnectionListParams) (pagination.OffsetPage[arcadego.UserConnectionResponse], error) +- client.Admin.UserConnections.List(ctx context.Context, query arcadego.AdminUserConnectionListParams) (\*pagination.OffsetPage[arcadego.UserConnectionResponse], error) - client.Admin.UserConnections.Delete(ctx context.Context, id string) error ## AuthProviders @@ -30,11 +30,11 @@ Response Types: Methods: -- client.Admin.AuthProviders.New(ctx context.Context, body arcadego.AdminAuthProviderNewParams) (arcadego.AuthProviderResponse, error) -- client.Admin.AuthProviders.List(ctx context.Context) (arcadego.AdminAuthProviderListResponse, error) -- client.Admin.AuthProviders.Delete(ctx context.Context, id string) (arcadego.AuthProviderResponse, error) -- client.Admin.AuthProviders.Get(ctx context.Context, id string) (arcadego.AuthProviderResponse, error) -- client.Admin.AuthProviders.Patch(ctx context.Context, id string, body arcadego.AdminAuthProviderPatchParams) (arcadego.AuthProviderResponse, error) +- client.Admin.AuthProviders.New(ctx context.Context, body arcadego.AdminAuthProviderNewParams) (\*arcadego.AuthProviderResponse, error) +- client.Admin.AuthProviders.List(ctx context.Context) (\*arcadego.AdminAuthProviderListResponse, error) +- client.Admin.AuthProviders.Delete(ctx context.Context, id string) (\*arcadego.AuthProviderResponse, error) +- client.Admin.AuthProviders.Get(ctx context.Context, id string) (\*arcadego.AuthProviderResponse, error) +- client.Admin.AuthProviders.Patch(ctx context.Context, id string, body arcadego.AdminAuthProviderPatchParams) (\*arcadego.AuthProviderResponse, error) ## Secrets @@ -45,7 +45,8 @@ Response Types: Methods: -- client.Admin.Secrets.List(ctx context.Context) (arcadego.AdminSecretListResponse, error) +- client.Admin.Secrets.New(ctx context.Context, secretKey string, body arcadego.AdminSecretNewParams) (\*arcadego.SecretResponse, error) +- client.Admin.Secrets.List(ctx context.Context) (\*arcadego.AdminSecretListResponse, error) - client.Admin.Secrets.Delete(ctx context.Context, secretID string) error # Auth @@ -61,9 +62,9 @@ Response Types: Methods: -- client.Auth.Authorize(ctx context.Context, body arcadego.AuthAuthorizeParams) (shared.AuthorizationResponse, error) -- client.Auth.ConfirmUser(ctx context.Context, body arcadego.AuthConfirmUserParams) (arcadego.ConfirmUserResponse, error) -- client.Auth.Status(ctx context.Context, query arcadego.AuthStatusParams) (shared.AuthorizationResponse, error) +- client.Auth.Authorize(ctx context.Context, body arcadego.AuthAuthorizeParams) (\*shared.AuthorizationResponse, error) +- client.Auth.ConfirmUser(ctx context.Context, body arcadego.AuthConfirmUserParams) (\*arcadego.ConfirmUserResponse, error) +- client.Auth.Status(ctx context.Context, query arcadego.AuthStatusParams) (\*shared.AuthorizationResponse, error) # Health @@ -73,7 +74,7 @@ Response Types: Methods: -- client.Health.Check(ctx context.Context) (arcadego.HealthSchema, error) +- client.Health.Check(ctx context.Context) (\*arcadego.HealthSchema, error) # Chat @@ -93,7 +94,7 @@ Response Types: Methods: -- client.Chat.Completions.New(ctx context.Context, body arcadego.ChatCompletionNewParams) (arcadego.ChatResponse, error) +- client.Chat.Completions.New(ctx context.Context, body arcadego.ChatCompletionNewParams) (\*arcadego.ChatResponse, error) # Tools @@ -112,10 +113,10 @@ Response Types: Methods: -- client.Tools.List(ctx context.Context, query arcadego.ToolListParams) (pagination.OffsetPage[arcadego.ToolDefinition], error) -- client.Tools.Authorize(ctx context.Context, body arcadego.ToolAuthorizeParams) (shared.AuthorizationResponse, error) -- client.Tools.Execute(ctx context.Context, body arcadego.ToolExecuteParams) (arcadego.ExecuteToolResponse, error) -- client.Tools.Get(ctx context.Context, name string, query arcadego.ToolGetParams) (arcadego.ToolDefinition, error) +- client.Tools.List(ctx context.Context, query arcadego.ToolListParams) (\*pagination.OffsetPage[arcadego.ToolDefinition], error) +- client.Tools.Authorize(ctx context.Context, body arcadego.ToolAuthorizeParams) (\*shared.AuthorizationResponse, error) +- client.Tools.Execute(ctx context.Context, body arcadego.ToolExecuteParams) (\*arcadego.ExecuteToolResponse, error) +- client.Tools.Get(ctx context.Context, name string, query arcadego.ToolGetParams) (\*arcadego.ToolDefinition, error) ## Scheduled @@ -125,8 +126,8 @@ Response Types: Methods: -- client.Tools.Scheduled.List(ctx context.Context, query arcadego.ToolScheduledListParams) (pagination.OffsetPage[arcadego.ToolExecution], error) -- client.Tools.Scheduled.Get(ctx context.Context, id string) (arcadego.ToolScheduledGetResponse, error) +- client.Tools.Scheduled.List(ctx context.Context, query arcadego.ToolScheduledListParams) (\*pagination.OffsetPage[arcadego.ToolExecution], error) +- client.Tools.Scheduled.Get(ctx context.Context, id string) (\*arcadego.ToolScheduledGetResponse, error) ## Formatted @@ -137,8 +138,8 @@ Response Types: Methods: -- client.Tools.Formatted.List(ctx context.Context, query arcadego.ToolFormattedListParams) (pagination.OffsetPage[arcadego.ToolFormattedListResponse], error) -- client.Tools.Formatted.Get(ctx context.Context, name string, query arcadego.ToolFormattedGetParams) (arcadego.ToolFormattedGetResponse, error) +- client.Tools.Formatted.List(ctx context.Context, query arcadego.ToolFormattedListParams) (\*pagination.OffsetPage[arcadego.ToolFormattedListResponse], error) +- client.Tools.Formatted.Get(ctx context.Context, name string, query arcadego.ToolFormattedGetParams) (\*arcadego.ToolFormattedGetResponse, error) # Workers @@ -154,10 +155,10 @@ Response Types: Methods: -- client.Workers.New(ctx context.Context, body arcadego.WorkerNewParams) (arcadego.WorkerResponse, error) -- client.Workers.Update(ctx context.Context, id string, body arcadego.WorkerUpdateParams) (arcadego.WorkerResponse, error) -- client.Workers.List(ctx context.Context, query arcadego.WorkerListParams) (pagination.OffsetPage[arcadego.WorkerResponse], error) +- client.Workers.New(ctx context.Context, body arcadego.WorkerNewParams) (\*arcadego.WorkerResponse, error) +- client.Workers.Update(ctx context.Context, id string, body arcadego.WorkerUpdateParams) (\*arcadego.WorkerResponse, error) +- client.Workers.List(ctx context.Context, query arcadego.WorkerListParams) (\*pagination.OffsetPage[arcadego.WorkerResponse], error) - client.Workers.Delete(ctx context.Context, id string) error -- client.Workers.Get(ctx context.Context, id string) (arcadego.WorkerResponse, error) -- client.Workers.Health(ctx context.Context, id string) (arcadego.WorkerHealthResponse, error) -- client.Workers.Tools(ctx context.Context, id string, query arcadego.WorkerToolsParams) (pagination.OffsetPage[arcadego.ToolDefinition], error) +- client.Workers.Get(ctx context.Context, id string) (\*arcadego.WorkerResponse, error) +- client.Workers.Health(ctx context.Context, id string) (\*arcadego.WorkerHealthResponse, error) +- client.Workers.Tools(ctx context.Context, id string, query arcadego.WorkerToolsParams) (\*pagination.OffsetPage[arcadego.ToolDefinition], error) diff --git a/auth.go b/auth.go index 3401e92..84da5b2 100644 --- a/auth.go +++ b/auth.go @@ -40,7 +40,7 @@ func (r *AuthService) Authorize(ctx context.Context, body AuthAuthorizeParams, o opts = slices.Concat(r.Options, opts) path := "v1/auth/authorize" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + return res, err } // Confirms a user's details during an authorization flow @@ -48,7 +48,7 @@ func (r *AuthService) ConfirmUser(ctx context.Context, body AuthConfirmUserParam opts = slices.Concat(r.Options, opts) path := "v1/auth/confirm_user" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + return res, err } // Checks the status of an ongoing authorization process for a specific tool. If @@ -58,12 +58,12 @@ func (r *AuthService) Status(ctx context.Context, query AuthStatusParams, opts . opts = slices.Concat(r.Options, opts) path := "v1/auth/status" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) - return + return res, err } type AuthRequestParam struct { - AuthRequirement param.Field[AuthRequestAuthRequirementParam] `json:"auth_requirement,required"` - UserID param.Field[string] `json:"user_id,required"` + AuthRequirement param.Field[AuthRequestAuthRequirementParam] `json:"auth_requirement" api:"required"` + UserID param.Field[string] `json:"user_id" api:"required"` // Optional: if provided, the user will be redirected to this URI after // authorization NextUri param.Field[string] `json:"next_uri"` @@ -95,8 +95,8 @@ func (r AuthRequestAuthRequirementOauth2Param) MarshalJSON() (data []byte, err e } type ConfirmUserRequestParam struct { - FlowID param.Field[string] `json:"flow_id,required"` - UserID param.Field[string] `json:"user_id,required"` + FlowID param.Field[string] `json:"flow_id" api:"required"` + UserID param.Field[string] `json:"user_id" api:"required"` } func (r ConfirmUserRequestParam) MarshalJSON() (data []byte, err error) { @@ -104,7 +104,7 @@ func (r ConfirmUserRequestParam) MarshalJSON() (data []byte, err error) { } type ConfirmUserResponse struct { - AuthID string `json:"auth_id,required"` + AuthID string `json:"auth_id" api:"required"` NextUri string `json:"next_uri"` JSON confirmUserResponseJSON `json:"-"` } @@ -127,7 +127,7 @@ func (r confirmUserResponseJSON) RawJSON() string { } type AuthAuthorizeParams struct { - AuthRequest AuthRequestParam `json:"auth_request,required"` + AuthRequest AuthRequestParam `json:"auth_request" api:"required"` } func (r AuthAuthorizeParams) MarshalJSON() (data []byte, err error) { @@ -135,7 +135,7 @@ func (r AuthAuthorizeParams) MarshalJSON() (data []byte, err error) { } type AuthConfirmUserParams struct { - ConfirmUserRequest ConfirmUserRequestParam `json:"confirm_user_request,required"` + ConfirmUserRequest ConfirmUserRequestParam `json:"confirm_user_request" api:"required"` } func (r AuthConfirmUserParams) MarshalJSON() (data []byte, err error) { @@ -144,7 +144,7 @@ func (r AuthConfirmUserParams) MarshalJSON() (data []byte, err error) { type AuthStatusParams struct { // Authorization ID - ID param.Field[string] `query:"id,required"` + ID param.Field[string] `query:"id" api:"required"` // Timeout in seconds (max 59) Wait param.Field[int64] `query:"wait"` } diff --git a/chat.go b/chat.go index 7d0b372..fba04b8 100644 --- a/chat.go +++ b/chat.go @@ -32,9 +32,9 @@ func NewChatService(opts ...option.RequestOption) (r *ChatService) { type ChatMessage struct { // The content of the message. - Content string `json:"content,required"` + Content string `json:"content" api:"required"` // The role of the author of this message. One of system, user, tool, or assistant. - Role string `json:"role,required"` + Role string `json:"role" api:"required"` // tool Name Name string `json:"name"` // tool_call_id @@ -127,9 +127,9 @@ func (r ChatMessageToolCallsType) IsKnown() bool { type ChatMessageParam struct { // The content of the message. - Content param.Field[string] `json:"content,required"` + Content param.Field[string] `json:"content" api:"required"` // The role of the author of this message. One of system, user, tool, or assistant. - Role param.Field[string] `json:"role,required"` + Role param.Field[string] `json:"role" api:"required"` // tool Name Name param.Field[string] `json:"name"` // tool_call_id diff --git a/chatcompletion.go b/chatcompletion.go index f619f7b..c89cebb 100644 --- a/chatcompletion.go +++ b/chatcompletion.go @@ -36,11 +36,11 @@ func (r *ChatCompletionService) New(ctx context.Context, body ChatCompletionNewP opts = slices.Concat(r.Options, opts) path := "v1/chat/completions" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + return res, err } type ChatCompletionNewParams struct { - ChatRequest ChatRequestParam `json:"chat_request,required"` + ChatRequest ChatRequestParam `json:"chat_request" api:"required"` } func (r ChatCompletionNewParams) MarshalJSON() (data []byte, err error) { diff --git a/client.go b/client.go index 51623c6..302a12a 100644 --- a/client.go +++ b/client.go @@ -7,6 +7,7 @@ import ( "net/http" "os" "slices" + "strings" "github.com/ArcadeAI/arcade-go/internal/requestconfig" "github.com/ArcadeAI/arcade-go/option" @@ -28,13 +29,21 @@ type Client struct { // DefaultClientOptions read from the environment (ARCADE_API_KEY, // ARCADE_BASE_URL). This should be used to initialize new clients. func DefaultClientOptions() []option.RequestOption { - defaults := []option.RequestOption{option.WithEnvironmentProduction()} + defaults := []option.RequestOption{option.WithHTTPClient(defaultHTTPClient()), option.WithEnvironmentProduction()} if o, ok := os.LookupEnv("ARCADE_BASE_URL"); ok { defaults = append(defaults, option.WithBaseURL(o)) } if o, ok := os.LookupEnv("ARCADE_API_KEY"); ok { defaults = append(defaults, option.WithAPIKey(o)) } + if o, ok := os.LookupEnv("ARCADE_CUSTOM_HEADERS"); ok { + for _, line := range strings.Split(o, "\n") { + colon := strings.Index(line, ":") + if colon >= 0 { + defaults = append(defaults, option.WithHeader(strings.TrimSpace(line[:colon]), strings.TrimSpace(line[colon+1:]))) + } + } + } return defaults } diff --git a/client_test.go b/client_test.go index 660cfcf..7051c67 100644 --- a/client_test.go +++ b/client_test.go @@ -38,7 +38,7 @@ func TestUserAgentHeader(t *testing.T) { }, }), ) - client.Chat.Completions.New(context.Background(), arcadego.ChatCompletionNewParams{ + _, _ = client.Chat.Completions.New(context.Background(), arcadego.ChatCompletionNewParams{ ChatRequest: arcadego.ChatRequestParam{}, }) if userAgent != fmt.Sprintf("Arcade/Go %s", internal.PackageVersion) { diff --git a/default_http_client.go b/default_http_client.go new file mode 100644 index 0000000..6d63eaa --- /dev/null +++ b/default_http_client.go @@ -0,0 +1,30 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package arcadego + +import ( + "net/http" + "time" +) + +// defaultResponseHeaderTimeout bounds the time between a fully written request +// and the server's response headers. It does not apply to the response body, +// so long-running streams are unaffected. Without this, a server that accepts +// the connection but never responds would hang the request indefinitely. +const defaultResponseHeaderTimeout = 10 * time.Minute + +// defaultHTTPClient returns an [*http.Client] used when the caller does not +// supply one via [option.WithHTTPClient]. When [http.DefaultTransport] is the +// stdlib [*http.Transport], it is cloned and a [http.Transport.ResponseHeaderTimeout] +// is set so stuck connections fail fast instead of compounding across retries. +// If [http.DefaultTransport] has been wrapped (for example by otelhttp for +// distributed tracing), the wrapping is preserved and the header timeout is +// skipped. +func defaultHTTPClient() *http.Client { + if t, ok := http.DefaultTransport.(*http.Transport); ok { + t = t.Clone() + t.ResponseHeaderTimeout = defaultResponseHeaderTimeout + return &http.Client{Transport: t} + } + return &http.Client{Transport: http.DefaultTransport} +} diff --git a/health.go b/health.go index 2007a09..4a36f4f 100644 --- a/health.go +++ b/health.go @@ -36,17 +36,20 @@ func (r *HealthService) Check(ctx context.Context, opts ...option.RequestOption) opts = slices.Concat(r.Options, opts) path := "v1/health" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return + return res, err } type HealthSchema struct { - Healthy bool `json:"healthy"` - JSON healthSchemaJSON `json:"-"` + Healthy bool `json:"healthy"` + // Optional: explains why unhealthy + Reason string `json:"reason"` + JSON healthSchemaJSON `json:"-"` } // healthSchemaJSON contains the JSON metadata for the struct [HealthSchema] type healthSchemaJSON struct { Healthy apijson.Field + Reason apijson.Field raw string ExtraFields map[string]apijson.Field } diff --git a/internal/apiform/encoder.go b/internal/apiform/encoder.go index 88bafff..890548c 100644 --- a/internal/apiform/encoder.go +++ b/internal/apiform/encoder.go @@ -42,7 +42,7 @@ type encoderField struct { } type encoderEntry struct { - reflect.Type + typ reflect.Type dateFormat string root bool } @@ -59,7 +59,7 @@ func (e *encoder) marshal(value interface{}, writer *multipart.Writer) error { func (e *encoder) typeEncoder(t reflect.Type) encoderFunc { entry := encoderEntry{ - Type: t, + typ: t, dateFormat: e.dateFormat, root: e.root, } diff --git a/internal/apiform/form_test.go b/internal/apiform/form_test.go index 39d1460..5ef4421 100644 --- a/internal/apiform/form_test.go +++ b/internal/apiform/form_test.go @@ -39,18 +39,18 @@ type DateTime struct { type AdditionalProperties struct { A bool `form:"a"` - Extras map[string]interface{} `form:"-,extras"` + Extras map[string]interface{} `form:"-" api:"extrafields"` } type TypedAdditionalProperties struct { A bool `form:"a"` - Extras map[string]int `form:"-,extras"` + Extras map[string]int `form:"-" api:"extrafields"` } type EmbeddedStructs struct { AdditionalProperties A *int `form:"number2"` - Extras map[string]interface{} `form:"-,extras"` + Extras map[string]interface{} `form:"-" api:"extrafields"` } type Recursive struct { diff --git a/internal/apiform/tag.go b/internal/apiform/tag.go index b22e054..b79da17 100644 --- a/internal/apiform/tag.go +++ b/internal/apiform/tag.go @@ -5,6 +5,7 @@ import ( "strings" ) +const apiStructTag = "api" const jsonStructTag = "json" const formStructTag = "form" const formatStructTag = "format" @@ -22,7 +23,7 @@ func parseFormStructTag(field reflect.StructField) (tag parsedStructTag, ok bool raw, ok = field.Tag.Lookup(jsonStructTag) } if !ok { - return + return tag, ok } parts := strings.Split(raw, ",") if len(parts) == 0 { @@ -39,10 +40,31 @@ func parseFormStructTag(field reflect.StructField) (tag parsedStructTag, ok bool tag.metadata = true } } - return + + // the `api` struct tag is only used alongside `json` for custom behaviour + parseApiStructTag(field, &tag) + return tag, ok +} + +func parseApiStructTag(field reflect.StructField, tag *parsedStructTag) { + raw, ok := field.Tag.Lookup(apiStructTag) + if !ok { + return + } + parts := strings.Split(raw, ",") + for _, part := range parts { + switch part { + case "extrafields": + tag.extras = true + case "required": + tag.required = true + case "metadata": + tag.metadata = true + } + } } func parseFormatStructTag(field reflect.StructField) (format string, ok bool) { format, ok = field.Tag.Lookup(formatStructTag) - return + return format, ok } diff --git a/internal/apijson/decoder.go b/internal/apijson/decoder.go index bf01bf6..f5fe7e3 100644 --- a/internal/apijson/decoder.go +++ b/internal/apijson/decoder.go @@ -75,7 +75,7 @@ type decoderField struct { } type decoderEntry struct { - reflect.Type + typ reflect.Type dateFormat string root bool } @@ -91,7 +91,7 @@ func (d *decoderBuilder) unmarshal(raw []byte, to any) error { func (d *decoderBuilder) typeDecoder(t reflect.Type) decoderFunc { entry := decoderEntry{ - Type: t, + typ: t, dateFormat: d.dateFormat, root: d.root, } diff --git a/internal/apijson/encoder.go b/internal/apijson/encoder.go index 350bc4f..55204a6 100644 --- a/internal/apijson/encoder.go +++ b/internal/apijson/encoder.go @@ -46,7 +46,7 @@ type encoderField struct { } type encoderEntry struct { - reflect.Type + typ reflect.Type dateFormat string root bool } @@ -63,7 +63,7 @@ func (e *encoder) marshal(value interface{}) ([]byte, error) { func (e *encoder) typeEncoder(t reflect.Type) encoderFunc { entry := encoderEntry{ - Type: t, + typ: t, dateFormat: e.dateFormat, root: e.root, } @@ -292,7 +292,7 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { return nil, err } } - return + return json, err } } diff --git a/internal/apijson/json_test.go b/internal/apijson/json_test.go index d4c6283..86dee4e 100644 --- a/internal/apijson/json_test.go +++ b/internal/apijson/json_test.go @@ -40,12 +40,12 @@ type DateTime struct { type AdditionalProperties struct { A bool `json:"a"` - ExtraFields map[string]interface{} `json:"-,extras"` + ExtraFields map[string]interface{} `json:"-" api:"extrafields"` } type TypedAdditionalProperties struct { A bool `json:"a"` - ExtraFields map[string]int `json:"-,extras"` + ExtraFields map[string]int `json:"-" api:"extrafields"` } type EmbeddedStruct struct { @@ -65,7 +65,7 @@ type EmbeddedStructJSON struct { type EmbeddedStructs struct { EmbeddedStruct A *int `json:"a"` - ExtraFields map[string]interface{} `json:"-,extras"` + ExtraFields map[string]interface{} `json:"-" api:"extrafields"` JSON EmbeddedStructsJSON } @@ -86,7 +86,7 @@ type JSONFieldStruct struct { B int64 `json:"b"` C string `json:"c"` D string `json:"d"` - ExtraFields map[string]int64 `json:"-,extras"` + ExtraFields map[string]int64 `json:"-" api:"extrafields"` JSON JSONFieldStructJSON `json:"-,metadata"` } @@ -268,7 +268,7 @@ type MarshallingUnionStruct struct { func (r *MarshallingUnionStruct) UnmarshalJSON(data []byte) (err error) { *r = MarshallingUnionStruct{} err = UnmarshalRoot(data, &r.Union) - return + return err } func (r MarshallingUnionStruct) MarshalJSON() (data []byte, err error) { diff --git a/internal/apijson/tag.go b/internal/apijson/tag.go index 812fb3c..17b2130 100644 --- a/internal/apijson/tag.go +++ b/internal/apijson/tag.go @@ -5,6 +5,7 @@ import ( "strings" ) +const apiStructTag = "api" const jsonStructTag = "json" const formatStructTag = "format" @@ -19,7 +20,7 @@ type parsedStructTag struct { func parseJSONStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) { raw, ok := field.Tag.Lookup(jsonStructTag) if !ok { - return + return tag, ok } parts := strings.Split(raw, ",") if len(parts) == 0 { @@ -38,10 +39,31 @@ func parseJSONStructTag(field reflect.StructField) (tag parsedStructTag, ok bool tag.inline = true } } - return + + // the `api` struct tag is only used alongside `json` for custom behaviour + parseApiStructTag(field, &tag) + return tag, ok +} + +func parseApiStructTag(field reflect.StructField, tag *parsedStructTag) { + raw, ok := field.Tag.Lookup(apiStructTag) + if !ok { + return + } + parts := strings.Split(raw, ",") + for _, part := range parts { + switch part { + case "extrafields": + tag.extras = true + case "required": + tag.required = true + case "metadata": + tag.metadata = true + } + } } func parseFormatStructTag(field reflect.StructField) (format string, ok bool) { format, ok = field.Tag.Lookup(formatStructTag) - return + return format, ok } diff --git a/internal/apiquery/encoder.go b/internal/apiquery/encoder.go index ed7fa0c..aa779fa 100644 --- a/internal/apiquery/encoder.go +++ b/internal/apiquery/encoder.go @@ -29,7 +29,7 @@ type encoderField struct { } type encoderEntry struct { - reflect.Type + typ reflect.Type dateFormat string root bool settings QuerySettings @@ -42,7 +42,7 @@ type Pair struct { func (e *encoder) typeEncoder(t reflect.Type) encoderFunc { entry := encoderEntry{ - Type: t, + typ: t, dateFormat: e.dateFormat, root: e.root, settings: e.settings, @@ -94,10 +94,10 @@ func (e *encoder) newTypeEncoder(t reflect.Type) encoderFunc { encoder := e.typeEncoder(t.Elem()) return func(key string, value reflect.Value) (pairs []Pair) { if !value.IsValid() || value.IsNil() { - return + return pairs } pairs = encoder(key, value.Elem()) - return + return pairs } case reflect.Struct: return e.newStructTypeEncoder(t) @@ -175,7 +175,7 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { field := value.FieldByIndex(ef.idx) pairs = append(pairs, ef.fn(subkey, field)...) } - return + return pairs } } @@ -193,7 +193,7 @@ func (e *encoder) newMapEncoder(t reflect.Type) encoderFunc { keyPath := e.renderKeyPath(key, subkey) pairs = append(pairs, elementEncoder(keyPath, iter.Value())...) } - return + return pairs } } @@ -232,7 +232,14 @@ func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc { return pairs } case ArrayQueryFormatIndices: - panic("The array indices format is not supported yet") + innerEncoder := e.typeEncoder(t.Elem()) + return func(key string, value reflect.Value) []Pair { + pairs := make([]Pair, 0, value.Len()) + for i := 0; i < value.Len(); i++ { + pairs = append(pairs, innerEncoder(fmt.Sprintf("%s[%d]", key, i), value.Index(i))...) + } + return pairs + } case ArrayQueryFormatBrackets: innerEncoder := e.typeEncoder(t.Elem()) return func(key string, value reflect.Value) []Pair { diff --git a/internal/apiquery/tag.go b/internal/apiquery/tag.go index 7ccd739..c96c373 100644 --- a/internal/apiquery/tag.go +++ b/internal/apiquery/tag.go @@ -17,7 +17,7 @@ type parsedStructTag struct { func parseQueryStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) { raw, ok := field.Tag.Lookup(queryStructTag) if !ok { - return + return tag, ok } parts := strings.Split(raw, ",") if len(parts) == 0 { @@ -32,10 +32,10 @@ func parseQueryStructTag(field reflect.StructField) (tag parsedStructTag, ok boo tag.inline = true } } - return + return tag, ok } func parseFormatStructTag(field reflect.StructField) (format string, ok bool) { format, ok = field.Tag.Lookup(formatStructTag) - return + return format, ok } diff --git a/internal/requestconfig/requestconfig.go b/internal/requestconfig/requestconfig.go index 4bccfaf..e4f4d3f 100644 --- a/internal/requestconfig/requestconfig.go +++ b/internal/requestconfig/requestconfig.go @@ -118,7 +118,13 @@ func NewRequestConfig(ctx context.Context, method string, u string, body interfa hasSerializationFunc = true params := body.URLQuery().Encode() if params != "" { - u = u + "?" + params + parsed, _ := url.Parse(u) + if parsed.RawQuery != "" { + parsed.RawQuery = parsed.RawQuery + "&" + params + u = parsed.String() + } else { + u = u + "?" + params + } } } if body, ok := body.([]byte); ok { @@ -359,11 +365,9 @@ func (b *bodyWithTimeout) Close() error { } func retryDelay(res *http.Response, retryCount int) time.Duration { - // If the API asks us to wait a certain amount of time (and it's a reasonable amount), - // just do what it says. - - if retryAfterDelay, ok := parseRetryAfterHeader(res); ok && 0 <= retryAfterDelay && retryAfterDelay < time.Minute { - return retryAfterDelay + // If the backend tells us to wait a certain amount of time, use that value + if retryAfterDelay, ok := parseRetryAfterHeader(res); ok { + return max(0, retryAfterDelay) } maxDelay := 8 * time.Second @@ -467,10 +471,14 @@ func (cfg *RequestConfig) Execute() (err error) { // Close the response body before retrying to prevent connection leaks if res != nil && res.Body != nil { - res.Body.Close() + _ = res.Body.Close() } - time.Sleep(retryDelay(res, retryCount)) + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(retryDelay(res, retryCount)): + } } // Save *http.Response if it is requested to, even if there was an error making the request. This is @@ -491,7 +499,7 @@ func (cfg *RequestConfig) Execute() (err error) { if res.StatusCode >= 400 { contents, err := io.ReadAll(res.Body) - res.Body.Close() + _ = res.Body.Close() if err != nil { return err } @@ -522,7 +530,7 @@ func (cfg *RequestConfig) Execute() (err error) { } contents, err := io.ReadAll(res.Body) - res.Body.Close() + _ = res.Body.Close() if err != nil { return fmt.Errorf("error reading response body: %w", err) } diff --git a/internal/testutil/testutil.go b/internal/testutil/testutil.go index 826d266..31103e9 100644 --- a/internal/testutil/testutil.go +++ b/internal/testutil/testutil.go @@ -16,10 +16,10 @@ func CheckTestServer(t *testing.T, url string) bool { t.Fatalf("strconv.ParseBool(os.LookupEnv(%s)) failed: %s", SKIP_MOCK_TESTS, err) } if skip { - t.Skip("The test will not run without a mock Prism server running against your OpenAPI spec") + t.Skip("The test will not run without a mock server running against your OpenAPI spec") return false } - t.Errorf("The test will not run without a mock Prism server running against your OpenAPI spec. You can set the environment variable %s to true to skip running any tests that require the mock server", SKIP_MOCK_TESTS) + t.Errorf("The test will not run without a mock server running against your OpenAPI spec. You can set the environment variable %s to true to skip running any tests that require the mock server", SKIP_MOCK_TESTS) return false } } diff --git a/internal/version.go b/internal/version.go index 64dcebb..81ac6eb 100644 --- a/internal/version.go +++ b/internal/version.go @@ -2,4 +2,4 @@ package internal -const PackageVersion = "0.1.0-alpha.8" // x-release-please-version +const PackageVersion = "0.1.0-alpha.9" // x-release-please-version diff --git a/option/middleware.go b/option/middleware.go index 8ec9dd6..4be0987 100644 --- a/option/middleware.go +++ b/option/middleware.go @@ -8,6 +8,10 @@ import ( "net/http/httputil" ) +// sensitiveLogHeaders are redacted before request and response content is +// written to the debug logger. +var sensitiveLogHeaders = []string{"authorization", "api-key", "x-api-key", "cookie", "set-cookie"} + // WithDebugLog logs the HTTP request and response content. // If the logger parameter is nil, it uses the default logger. // @@ -20,7 +24,7 @@ func WithDebugLog(logger *log.Logger) RequestOption { logger = log.Default() } - if reqBytes, err := httputil.DumpRequest(req, true); err == nil { + if reqBytes, err := dumpRedactedRequest(req); err == nil { logger.Printf("Request Content:\n%s\n", reqBytes) } @@ -29,10 +33,48 @@ func WithDebugLog(logger *log.Logger) RequestOption { return resp, err } - if respBytes, err := httputil.DumpResponse(resp, true); err == nil { + if respBytes, err := dumpRedactedResponse(resp); err == nil { logger.Printf("Response Content:\n%s\n", respBytes) } return resp, err }) } + +// dumpRedactedRequest dumps req with sensitive headers replaced. The +// original headers are restored via defer so a panic in DumpRequest cannot +// leak the placeholder map into the live request sent downstream. +func dumpRedactedRequest(req *http.Request) ([]byte, error) { + origHeaders := req.Header + req.Header = redactDebugHeaders(origHeaders) + defer func() { req.Header = origHeaders }() + return httputil.DumpRequest(req, true) +} + +func dumpRedactedResponse(resp *http.Response) ([]byte, error) { + origHeaders := resp.Header + resp.Header = redactDebugHeaders(origHeaders) + defer func() { resp.Header = origHeaders }() + return httputil.DumpResponse(resp, true) +} + +func redactDebugHeaders(headers http.Header) http.Header { + var redacted http.Header + for _, name := range sensitiveLogHeaders { + values := headers.Values(name) + if len(values) == 0 { + continue + } + if redacted == nil { + redacted = headers.Clone() + } + redacted.Del(name) + for range values { + redacted.Add(name, "***") + } + } + if redacted == nil { + return headers + } + return redacted +} diff --git a/packages/pagination/pagination.go b/packages/pagination/pagination.go index 930d57a..e3e3e02 100644 --- a/packages/pagination/pagination.go +++ b/packages/pagination/pagination.go @@ -46,7 +46,9 @@ func (r *OffsetPage[T]) GetNextPage() (res *OffsetPage[T], err error) { } cfg := r.cfg.Clone(r.cfg.Context) - next := r.Offset + offset := r.Offset + length := int64(len(r.Items)) + next := offset + length if next < r.TotalCount && next != 0 { err = cfg.Apply(option.WithQuery("offset", strconv.FormatInt(next, 10))) diff --git a/paginationauto_test.go b/paginationauto_test.go new file mode 100644 index 0000000..8ff5898 --- /dev/null +++ b/paginationauto_test.go @@ -0,0 +1,36 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package arcadego_test + +import ( + "context" + "os" + "testing" + + "github.com/ArcadeAI/arcade-go" + "github.com/ArcadeAI/arcade-go/internal/testutil" + "github.com/ArcadeAI/arcade-go/option" +) + +func TestAutoPagination(t *testing.T) { + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := arcadego.NewClient( + option.WithBaseURL(baseURL), + option.WithAPIKey("My API Key"), + ) + iter := client.Admin.UserConnections.ListAutoPaging(context.TODO(), arcadego.AdminUserConnectionListParams{}) + // The mock server isn't going to give us real pagination + for i := 0; i < 3 && iter.Next(); i++ { + userConnection := iter.Current() + t.Logf("%+v\n", userConnection.ID) + } + if err := iter.Err(); err != nil { + t.Fatalf("err should be nil: %s", err.Error()) + } +} diff --git a/paginationmanual_test.go b/paginationmanual_test.go new file mode 100644 index 0000000..6666593 --- /dev/null +++ b/paginationmanual_test.go @@ -0,0 +1,44 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package arcadego_test + +import ( + "context" + "os" + "testing" + + "github.com/ArcadeAI/arcade-go" + "github.com/ArcadeAI/arcade-go/internal/testutil" + "github.com/ArcadeAI/arcade-go/option" +) + +func TestManualPagination(t *testing.T) { + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := arcadego.NewClient( + option.WithBaseURL(baseURL), + option.WithAPIKey("My API Key"), + ) + page, err := client.Admin.UserConnections.List(context.TODO(), arcadego.AdminUserConnectionListParams{}) + if err != nil { + t.Fatalf("err should be nil: %s", err.Error()) + } + for _, userConnection := range page.Items { + t.Logf("%+v\n", userConnection.ID) + } + // The mock server isn't going to give us real pagination + page, err = page.GetNextPage() + if err != nil { + t.Fatalf("err should be nil: %s", err.Error()) + } + if page != nil { + for _, userConnection := range page.Items { + t.Logf("%+v\n", userConnection.ID) + } + } +} diff --git a/scripts/bootstrap b/scripts/bootstrap index 5ab3066..46547f1 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,7 +4,7 @@ set -e cd "$(dirname "$0")/.." -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "${SKIP_BREW:-}" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { echo -n "==> Install Homebrew dependencies? (y/N): " read -r response diff --git a/scripts/mock b/scripts/mock index 0b28f6e..feebe5e 100755 --- a/scripts/mock +++ b/scripts/mock @@ -19,23 +19,34 @@ fi echo "==> Starting mock server with URL ${URL}" -# Run prism mock on the given spec +# Run steady mock on the given spec if [ "$1" == "--daemon" ]; then - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & + # Pre-install the package so the download doesn't eat into the startup timeout + npm exec --package=@stdy/cli@0.22.1 -- steady --version - # Wait for server to come online + npm exec --package=@stdy/cli@0.22.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-form-array-format=comma --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" &> .stdy.log & + + # Wait for server to come online via health endpoint (max 30s) echo -n "Waiting for server" - while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do + attempts=0 + while ! curl --silent --fail "http://127.0.0.1:4010/_x-steady/health" >/dev/null 2>&1; do + if ! kill -0 $! 2>/dev/null; then + echo + cat .stdy.log + exit 1 + fi + attempts=$((attempts + 1)) + if [ "$attempts" -ge 300 ]; then + echo + echo "Timed out waiting for Steady server to start" + cat .stdy.log + exit 1 + fi echo -n "." sleep 0.1 done - if grep -q "✖ fatal" ".prism.log"; then - cat .prism.log - exit 1 - fi - echo else - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" + npm exec --package=@stdy/cli@0.22.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-form-array-format=comma --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" fi diff --git a/scripts/test b/scripts/test index c26b122..14cb1af 100755 --- a/scripts/test +++ b/scripts/test @@ -9,8 +9,8 @@ GREEN='\033[0;32m' YELLOW='\033[0;33m' NC='\033[0m' # No Color -function prism_is_running() { - curl --silent "http://localhost:4010" >/dev/null 2>&1 +function steady_is_running() { + curl --silent "http://127.0.0.1:4010/_x-steady/health" >/dev/null 2>&1 } kill_server_on_port() { @@ -25,7 +25,7 @@ function is_overriding_api_base_url() { [ -n "$TEST_API_BASE_URL" ] } -if ! is_overriding_api_base_url && ! prism_is_running ; then +if ! is_overriding_api_base_url && ! steady_is_running ; then # When we exit this script, make sure to kill the background mock server process trap 'kill_server_on_port 4010' EXIT @@ -36,19 +36,19 @@ fi if is_overriding_api_base_url ; then echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" echo -elif ! prism_is_running ; then - echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" +elif ! steady_is_running ; then + echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Steady server" echo -e "running against your OpenAPI spec." echo echo -e "To run the server, pass in the path or url of your OpenAPI" - echo -e "spec to the prism command:" + echo -e "spec to the steady command:" echo - echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}" + echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.22.1 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-form-array-format=comma --validator-query-object-format=brackets --validator-form-object-format=brackets${NC}" echo exit 1 else - echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" + echo -e "${GREEN}✔ Mock steady server is running with your OpenAPI spec${NC}" echo fi diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh new file mode 100755 index 0000000..d5f0226 --- /dev/null +++ b/scripts/utils/upload-artifact.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -exuo pipefail + +DIST_DIR="dist" +FILENAME="source.zip" + +mapfile -d '' files < <( + find . -type f \ + \( -name '*.go' -o -name 'go.mod' -o -name 'go.sum' \) \ + ! -path "./${DIST_DIR}/*" \ + -print0 +) + +if [[ ${#files[@]} -eq 0 ]]; then + echo -e "\033[31mNo Go source files found for packaging.\033[0m" + exit 1 +fi + +mkdir -p "$DIST_DIR" +rm -f "${DIST_DIR}/${FILENAME}" + +relative_files=() +for file in "${files[@]}"; do + relative_files+=("${file#./}") +done + +zip "${DIST_DIR}/${FILENAME}" "${relative_files[@]}" + +RESPONSE=$(curl -X POST "$URL?filename=$FILENAME" \ + -H "Authorization: Bearer $AUTH" \ + -H "Content-Type: application/json") + +SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') + +if [[ "$SIGNED_URL" == "null" ]]; then + echo -e "\033[31mFailed to get signed URL.\033[0m" + exit 1 +fi + +UPLOAD_RESPONSE=$(curl -v -X PUT \ + -H "Content-Type: application/zip" \ + --data-binary "@${DIST_DIR}/${FILENAME}" "$SIGNED_URL" 2>&1) + +if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then + echo -e "\033[32mUploaded build to Stainless storage.\033[0m" + echo -e "\033[32mInstallation: Download and unzip: 'https://pkg.stainless.com/s/arcade-engine-go/$SHA'. Run 'go mod edit -replace github.com/ArcadeAI/arcade-go=/path/to/unzipped_directory'.\033[0m" +else + echo -e "\033[31mFailed to upload artifact.\033[0m" + exit 1 +fi diff --git a/tool.go b/tool.go index 4efed6c..b6b8dc7 100644 --- a/tool.go +++ b/tool.go @@ -43,7 +43,7 @@ func NewToolService(opts ...option.RequestOption) (r *ToolService) { } // Returns a page of tools from the engine configuration, optionally filtered by -// toolkit +// toolkit and/or metadata func (r *ToolService) List(ctx context.Context, query ToolListParams, opts ...option.RequestOption) (res *pagination.OffsetPage[ToolDefinition], err error) { var raw *http.Response opts = slices.Concat(r.Options, opts) @@ -62,7 +62,7 @@ func (r *ToolService) List(ctx context.Context, query ToolListParams, opts ...op } // Returns a page of tools from the engine configuration, optionally filtered by -// toolkit +// toolkit and/or metadata func (r *ToolService) ListAutoPaging(ctx context.Context, query ToolListParams, opts ...option.RequestOption) *pagination.OffsetPageAutoPager[ToolDefinition] { return pagination.NewOffsetPageAutoPager(r.List(ctx, query, opts...)) } @@ -72,7 +72,7 @@ func (r *ToolService) Authorize(ctx context.Context, body ToolAuthorizeParams, o opts = slices.Concat(r.Options, opts) path := "v1/tools/authorize" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + return res, err } // Executes a tool by name and arguments @@ -80,7 +80,7 @@ func (r *ToolService) Execute(ctx context.Context, body ToolExecuteParams, opts opts = slices.Concat(r.Options, opts) path := "v1/tools/execute" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + return res, err } // Returns the arcade tool specification for a specific tool @@ -88,15 +88,15 @@ func (r *ToolService) Get(ctx context.Context, name string, query ToolGetParams, opts = slices.Concat(r.Options, opts) if name == "" { err = errors.New("missing required name parameter") - return + return nil, err } path := fmt.Sprintf("v1/tools/%s", name) err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) - return + return res, err } type AuthorizeToolRequestParam struct { - ToolName param.Field[string] `json:"tool_name,required"` + ToolName param.Field[string] `json:"tool_name" api:"required"` // Optional: if provided, the user will be redirected to this URI after // authorization NextUri param.Field[string] `json:"next_uri"` @@ -111,7 +111,7 @@ func (r AuthorizeToolRequestParam) MarshalJSON() (data []byte, err error) { } type ExecuteToolRequestParam struct { - ToolName param.Field[string] `json:"tool_name,required"` + ToolName param.Field[string] `json:"tool_name" api:"required"` // Whether to include the error stacktrace in the response. If not provided, the // error stacktrace is not included. IncludeErrorStacktrace param.Field[bool] `json:"include_error_stacktrace"` @@ -197,9 +197,9 @@ func (r executeToolResponseOutputJSON) RawJSON() string { } type ExecuteToolResponseOutputError struct { - CanRetry bool `json:"can_retry,required"` - Kind ExecuteToolResponseOutputErrorKind `json:"kind,required"` - Message string `json:"message,required"` + CanRetry bool `json:"can_retry" api:"required"` + Kind ExecuteToolResponseOutputErrorKind `json:"kind" api:"required"` + Message string `json:"message" api:"required"` AdditionalPromptContent string `json:"additional_prompt_content"` DeveloperMessage string `json:"developer_message"` Extra map[string]interface{} `json:"extra"` @@ -236,37 +236,42 @@ func (r executeToolResponseOutputErrorJSON) RawJSON() string { type ExecuteToolResponseOutputErrorKind string const ( - ExecuteToolResponseOutputErrorKindToolkitLoadFailed ExecuteToolResponseOutputErrorKind = "TOOLKIT_LOAD_FAILED" - ExecuteToolResponseOutputErrorKindToolDefinitionBadDefinition ExecuteToolResponseOutputErrorKind = "TOOL_DEFINITION_BAD_DEFINITION" - ExecuteToolResponseOutputErrorKindToolDefinitionBadInputSchema ExecuteToolResponseOutputErrorKind = "TOOL_DEFINITION_BAD_INPUT_SCHEMA" - ExecuteToolResponseOutputErrorKindToolDefinitionBadOutputSchema ExecuteToolResponseOutputErrorKind = "TOOL_DEFINITION_BAD_OUTPUT_SCHEMA" - ExecuteToolResponseOutputErrorKindToolRequirementsNotMet ExecuteToolResponseOutputErrorKind = "TOOL_REQUIREMENTS_NOT_MET" - ExecuteToolResponseOutputErrorKindToolRuntimeBadInputValue ExecuteToolResponseOutputErrorKind = "TOOL_RUNTIME_BAD_INPUT_VALUE" - ExecuteToolResponseOutputErrorKindToolRuntimeBadOutputValue ExecuteToolResponseOutputErrorKind = "TOOL_RUNTIME_BAD_OUTPUT_VALUE" - ExecuteToolResponseOutputErrorKindToolRuntimeRetry ExecuteToolResponseOutputErrorKind = "TOOL_RUNTIME_RETRY" - ExecuteToolResponseOutputErrorKindToolRuntimeContextRequired ExecuteToolResponseOutputErrorKind = "TOOL_RUNTIME_CONTEXT_REQUIRED" - ExecuteToolResponseOutputErrorKindToolRuntimeFatal ExecuteToolResponseOutputErrorKind = "TOOL_RUNTIME_FATAL" - ExecuteToolResponseOutputErrorKindUpstreamRuntimeBadRequest ExecuteToolResponseOutputErrorKind = "UPSTREAM_RUNTIME_BAD_REQUEST" - ExecuteToolResponseOutputErrorKindUpstreamRuntimeAuthError ExecuteToolResponseOutputErrorKind = "UPSTREAM_RUNTIME_AUTH_ERROR" - ExecuteToolResponseOutputErrorKindUpstreamRuntimeNotFound ExecuteToolResponseOutputErrorKind = "UPSTREAM_RUNTIME_NOT_FOUND" - ExecuteToolResponseOutputErrorKindUpstreamRuntimeValidationError ExecuteToolResponseOutputErrorKind = "UPSTREAM_RUNTIME_VALIDATION_ERROR" - ExecuteToolResponseOutputErrorKindUpstreamRuntimeRateLimit ExecuteToolResponseOutputErrorKind = "UPSTREAM_RUNTIME_RATE_LIMIT" - ExecuteToolResponseOutputErrorKindUpstreamRuntimeServerError ExecuteToolResponseOutputErrorKind = "UPSTREAM_RUNTIME_SERVER_ERROR" - ExecuteToolResponseOutputErrorKindUpstreamRuntimeUnmapped ExecuteToolResponseOutputErrorKind = "UPSTREAM_RUNTIME_UNMAPPED" - ExecuteToolResponseOutputErrorKindUnknown ExecuteToolResponseOutputErrorKind = "UNKNOWN" + ExecuteToolResponseOutputErrorKindToolkitLoadFailed ExecuteToolResponseOutputErrorKind = "TOOLKIT_LOAD_FAILED" + ExecuteToolResponseOutputErrorKindToolDefinitionBadDefinition ExecuteToolResponseOutputErrorKind = "TOOL_DEFINITION_BAD_DEFINITION" + ExecuteToolResponseOutputErrorKindToolDefinitionBadInputSchema ExecuteToolResponseOutputErrorKind = "TOOL_DEFINITION_BAD_INPUT_SCHEMA" + ExecuteToolResponseOutputErrorKindToolDefinitionBadOutputSchema ExecuteToolResponseOutputErrorKind = "TOOL_DEFINITION_BAD_OUTPUT_SCHEMA" + ExecuteToolResponseOutputErrorKindToolRequirementsNotMet ExecuteToolResponseOutputErrorKind = "TOOL_REQUIREMENTS_NOT_MET" + ExecuteToolResponseOutputErrorKindToolRuntimeBadInputValue ExecuteToolResponseOutputErrorKind = "TOOL_RUNTIME_BAD_INPUT_VALUE" + ExecuteToolResponseOutputErrorKindToolRuntimeBadOutputValue ExecuteToolResponseOutputErrorKind = "TOOL_RUNTIME_BAD_OUTPUT_VALUE" + ExecuteToolResponseOutputErrorKindToolRuntimeRetry ExecuteToolResponseOutputErrorKind = "TOOL_RUNTIME_RETRY" + ExecuteToolResponseOutputErrorKindToolRuntimeContextRequired ExecuteToolResponseOutputErrorKind = "TOOL_RUNTIME_CONTEXT_REQUIRED" + ExecuteToolResponseOutputErrorKindToolRuntimeFatal ExecuteToolResponseOutputErrorKind = "TOOL_RUNTIME_FATAL" + ExecuteToolResponseOutputErrorKindContextCheckFailed ExecuteToolResponseOutputErrorKind = "CONTEXT_CHECK_FAILED" + ExecuteToolResponseOutputErrorKindContextDenied ExecuteToolResponseOutputErrorKind = "CONTEXT_DENIED" + ExecuteToolResponseOutputErrorKindUpstreamRuntimeBadRequest ExecuteToolResponseOutputErrorKind = "UPSTREAM_RUNTIME_BAD_REQUEST" + ExecuteToolResponseOutputErrorKindUpstreamRuntimeAuthError ExecuteToolResponseOutputErrorKind = "UPSTREAM_RUNTIME_AUTH_ERROR" + ExecuteToolResponseOutputErrorKindUpstreamRuntimeNotFound ExecuteToolResponseOutputErrorKind = "UPSTREAM_RUNTIME_NOT_FOUND" + ExecuteToolResponseOutputErrorKindUpstreamRuntimeValidationError ExecuteToolResponseOutputErrorKind = "UPSTREAM_RUNTIME_VALIDATION_ERROR" + ExecuteToolResponseOutputErrorKindUpstreamRuntimeRateLimit ExecuteToolResponseOutputErrorKind = "UPSTREAM_RUNTIME_RATE_LIMIT" + ExecuteToolResponseOutputErrorKindUpstreamRuntimeServerError ExecuteToolResponseOutputErrorKind = "UPSTREAM_RUNTIME_SERVER_ERROR" + ExecuteToolResponseOutputErrorKindUpstreamRuntimeUnmapped ExecuteToolResponseOutputErrorKind = "UPSTREAM_RUNTIME_UNMAPPED" + ExecuteToolResponseOutputErrorKindNetworkTransportRuntimeTimeout ExecuteToolResponseOutputErrorKind = "NETWORK_TRANSPORT_RUNTIME_TIMEOUT" + ExecuteToolResponseOutputErrorKindNetworkTransportRuntimeUnreachable ExecuteToolResponseOutputErrorKind = "NETWORK_TRANSPORT_RUNTIME_UNREACHABLE" + ExecuteToolResponseOutputErrorKindNetworkTransportRuntimeUnmapped ExecuteToolResponseOutputErrorKind = "NETWORK_TRANSPORT_RUNTIME_UNMAPPED" + ExecuteToolResponseOutputErrorKindUnknown ExecuteToolResponseOutputErrorKind = "UNKNOWN" ) func (r ExecuteToolResponseOutputErrorKind) IsKnown() bool { switch r { - case ExecuteToolResponseOutputErrorKindToolkitLoadFailed, ExecuteToolResponseOutputErrorKindToolDefinitionBadDefinition, ExecuteToolResponseOutputErrorKindToolDefinitionBadInputSchema, ExecuteToolResponseOutputErrorKindToolDefinitionBadOutputSchema, ExecuteToolResponseOutputErrorKindToolRequirementsNotMet, ExecuteToolResponseOutputErrorKindToolRuntimeBadInputValue, ExecuteToolResponseOutputErrorKindToolRuntimeBadOutputValue, ExecuteToolResponseOutputErrorKindToolRuntimeRetry, ExecuteToolResponseOutputErrorKindToolRuntimeContextRequired, ExecuteToolResponseOutputErrorKindToolRuntimeFatal, ExecuteToolResponseOutputErrorKindUpstreamRuntimeBadRequest, ExecuteToolResponseOutputErrorKindUpstreamRuntimeAuthError, ExecuteToolResponseOutputErrorKindUpstreamRuntimeNotFound, ExecuteToolResponseOutputErrorKindUpstreamRuntimeValidationError, ExecuteToolResponseOutputErrorKindUpstreamRuntimeRateLimit, ExecuteToolResponseOutputErrorKindUpstreamRuntimeServerError, ExecuteToolResponseOutputErrorKindUpstreamRuntimeUnmapped, ExecuteToolResponseOutputErrorKindUnknown: + case ExecuteToolResponseOutputErrorKindToolkitLoadFailed, ExecuteToolResponseOutputErrorKindToolDefinitionBadDefinition, ExecuteToolResponseOutputErrorKindToolDefinitionBadInputSchema, ExecuteToolResponseOutputErrorKindToolDefinitionBadOutputSchema, ExecuteToolResponseOutputErrorKindToolRequirementsNotMet, ExecuteToolResponseOutputErrorKindToolRuntimeBadInputValue, ExecuteToolResponseOutputErrorKindToolRuntimeBadOutputValue, ExecuteToolResponseOutputErrorKindToolRuntimeRetry, ExecuteToolResponseOutputErrorKindToolRuntimeContextRequired, ExecuteToolResponseOutputErrorKindToolRuntimeFatal, ExecuteToolResponseOutputErrorKindContextCheckFailed, ExecuteToolResponseOutputErrorKindContextDenied, ExecuteToolResponseOutputErrorKindUpstreamRuntimeBadRequest, ExecuteToolResponseOutputErrorKindUpstreamRuntimeAuthError, ExecuteToolResponseOutputErrorKindUpstreamRuntimeNotFound, ExecuteToolResponseOutputErrorKindUpstreamRuntimeValidationError, ExecuteToolResponseOutputErrorKindUpstreamRuntimeRateLimit, ExecuteToolResponseOutputErrorKindUpstreamRuntimeServerError, ExecuteToolResponseOutputErrorKindUpstreamRuntimeUnmapped, ExecuteToolResponseOutputErrorKindNetworkTransportRuntimeTimeout, ExecuteToolResponseOutputErrorKindNetworkTransportRuntimeUnreachable, ExecuteToolResponseOutputErrorKindNetworkTransportRuntimeUnmapped, ExecuteToolResponseOutputErrorKindUnknown: return true } return false } type ExecuteToolResponseOutputLog struct { - Level string `json:"level,required"` - Message string `json:"message,required"` + Level string `json:"level" api:"required"` + Message string `json:"message" api:"required"` Subtype string `json:"subtype"` JSON executeToolResponseOutputLogJSON `json:"-"` } @@ -290,13 +295,14 @@ func (r executeToolResponseOutputLogJSON) RawJSON() string { } type ToolDefinition struct { - FullyQualifiedName string `json:"fully_qualified_name,required"` - Input ToolDefinitionInput `json:"input,required"` - Name string `json:"name,required"` - QualifiedName string `json:"qualified_name,required"` - Toolkit ToolDefinitionToolkit `json:"toolkit,required"` + FullyQualifiedName string `json:"fully_qualified_name" api:"required"` + Input ToolDefinitionInput `json:"input" api:"required"` + Name string `json:"name" api:"required"` + QualifiedName string `json:"qualified_name" api:"required"` + Toolkit ToolDefinitionToolkit `json:"toolkit" api:"required"` Description string `json:"description"` FormattedSchema map[string]interface{} `json:"formatted_schema"` + Metadata ToolDefinitionMetadata `json:"metadata"` Output ToolDefinitionOutput `json:"output"` Requirements ToolDefinitionRequirements `json:"requirements"` JSON toolDefinitionJSON `json:"-"` @@ -311,6 +317,7 @@ type toolDefinitionJSON struct { Toolkit apijson.Field Description apijson.Field FormattedSchema apijson.Field + Metadata apijson.Field Output apijson.Field Requirements apijson.Field raw string @@ -347,8 +354,8 @@ func (r toolDefinitionInputJSON) RawJSON() string { } type ToolDefinitionInputParameter struct { - Name string `json:"name,required"` - ValueSchema ValueSchema `json:"value_schema,required"` + Name string `json:"name" api:"required"` + ValueSchema ValueSchema `json:"value_schema" api:"required"` Description string `json:"description"` Inferrable bool `json:"inferrable"` Required bool `json:"required"` @@ -376,7 +383,7 @@ func (r toolDefinitionInputParameterJSON) RawJSON() string { } type ToolDefinitionToolkit struct { - Name string `json:"name,required"` + Name string `json:"name" api:"required"` Description string `json:"description"` Version string `json:"version"` JSON toolDefinitionToolkitJSON `json:"-"` @@ -400,6 +407,81 @@ func (r toolDefinitionToolkitJSON) RawJSON() string { return r.raw } +type ToolDefinitionMetadata struct { + Behavior ToolDefinitionMetadataBehavior `json:"behavior"` + Classification ToolDefinitionMetadataClassification `json:"classification"` + Extras map[string]interface{} `json:"extras"` + JSON toolDefinitionMetadataJSON `json:"-"` +} + +// toolDefinitionMetadataJSON contains the JSON metadata for the struct +// [ToolDefinitionMetadata] +type toolDefinitionMetadataJSON struct { + Behavior apijson.Field + Classification apijson.Field + Extras apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ToolDefinitionMetadata) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r toolDefinitionMetadataJSON) RawJSON() string { + return r.raw +} + +type ToolDefinitionMetadataBehavior struct { + Destructive bool `json:"destructive"` + Idempotent bool `json:"idempotent"` + OpenWorld bool `json:"open_world"` + Operations []string `json:"operations"` + ReadOnly bool `json:"read_only"` + JSON toolDefinitionMetadataBehaviorJSON `json:"-"` +} + +// toolDefinitionMetadataBehaviorJSON contains the JSON metadata for the struct +// [ToolDefinitionMetadataBehavior] +type toolDefinitionMetadataBehaviorJSON struct { + Destructive apijson.Field + Idempotent apijson.Field + OpenWorld apijson.Field + Operations apijson.Field + ReadOnly apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ToolDefinitionMetadataBehavior) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r toolDefinitionMetadataBehaviorJSON) RawJSON() string { + return r.raw +} + +type ToolDefinitionMetadataClassification struct { + ServiceDomains []string `json:"service_domains"` + JSON toolDefinitionMetadataClassificationJSON `json:"-"` +} + +// toolDefinitionMetadataClassificationJSON contains the JSON metadata for the +// struct [ToolDefinitionMetadataClassification] +type toolDefinitionMetadataClassificationJSON struct { + ServiceDomains apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ToolDefinitionMetadataClassification) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r toolDefinitionMetadataClassificationJSON) RawJSON() string { + return r.raw +} + type ToolDefinitionOutput struct { AvailableModes []string `json:"available_modes"` Description string `json:"description"` @@ -537,7 +619,7 @@ func (r ToolDefinitionRequirementsAuthorizationTokenStatus) IsKnown() bool { } type ToolDefinitionRequirementsSecret struct { - Key string `json:"key,required"` + Key string `json:"key" api:"required"` Met bool `json:"met"` StatusReason string `json:"status_reason"` JSON toolDefinitionRequirementsSecretJSON `json:"-"` @@ -662,9 +744,9 @@ func (r toolExecutionAttemptOutputJSON) RawJSON() string { } type ToolExecutionAttemptOutputError struct { - CanRetry bool `json:"can_retry,required"` - Kind ToolExecutionAttemptOutputErrorKind `json:"kind,required"` - Message string `json:"message,required"` + CanRetry bool `json:"can_retry" api:"required"` + Kind ToolExecutionAttemptOutputErrorKind `json:"kind" api:"required"` + Message string `json:"message" api:"required"` AdditionalPromptContent string `json:"additional_prompt_content"` DeveloperMessage string `json:"developer_message"` Extra map[string]interface{} `json:"extra"` @@ -701,37 +783,42 @@ func (r toolExecutionAttemptOutputErrorJSON) RawJSON() string { type ToolExecutionAttemptOutputErrorKind string const ( - ToolExecutionAttemptOutputErrorKindToolkitLoadFailed ToolExecutionAttemptOutputErrorKind = "TOOLKIT_LOAD_FAILED" - ToolExecutionAttemptOutputErrorKindToolDefinitionBadDefinition ToolExecutionAttemptOutputErrorKind = "TOOL_DEFINITION_BAD_DEFINITION" - ToolExecutionAttemptOutputErrorKindToolDefinitionBadInputSchema ToolExecutionAttemptOutputErrorKind = "TOOL_DEFINITION_BAD_INPUT_SCHEMA" - ToolExecutionAttemptOutputErrorKindToolDefinitionBadOutputSchema ToolExecutionAttemptOutputErrorKind = "TOOL_DEFINITION_BAD_OUTPUT_SCHEMA" - ToolExecutionAttemptOutputErrorKindToolRequirementsNotMet ToolExecutionAttemptOutputErrorKind = "TOOL_REQUIREMENTS_NOT_MET" - ToolExecutionAttemptOutputErrorKindToolRuntimeBadInputValue ToolExecutionAttemptOutputErrorKind = "TOOL_RUNTIME_BAD_INPUT_VALUE" - ToolExecutionAttemptOutputErrorKindToolRuntimeBadOutputValue ToolExecutionAttemptOutputErrorKind = "TOOL_RUNTIME_BAD_OUTPUT_VALUE" - ToolExecutionAttemptOutputErrorKindToolRuntimeRetry ToolExecutionAttemptOutputErrorKind = "TOOL_RUNTIME_RETRY" - ToolExecutionAttemptOutputErrorKindToolRuntimeContextRequired ToolExecutionAttemptOutputErrorKind = "TOOL_RUNTIME_CONTEXT_REQUIRED" - ToolExecutionAttemptOutputErrorKindToolRuntimeFatal ToolExecutionAttemptOutputErrorKind = "TOOL_RUNTIME_FATAL" - ToolExecutionAttemptOutputErrorKindUpstreamRuntimeBadRequest ToolExecutionAttemptOutputErrorKind = "UPSTREAM_RUNTIME_BAD_REQUEST" - ToolExecutionAttemptOutputErrorKindUpstreamRuntimeAuthError ToolExecutionAttemptOutputErrorKind = "UPSTREAM_RUNTIME_AUTH_ERROR" - ToolExecutionAttemptOutputErrorKindUpstreamRuntimeNotFound ToolExecutionAttemptOutputErrorKind = "UPSTREAM_RUNTIME_NOT_FOUND" - ToolExecutionAttemptOutputErrorKindUpstreamRuntimeValidationError ToolExecutionAttemptOutputErrorKind = "UPSTREAM_RUNTIME_VALIDATION_ERROR" - ToolExecutionAttemptOutputErrorKindUpstreamRuntimeRateLimit ToolExecutionAttemptOutputErrorKind = "UPSTREAM_RUNTIME_RATE_LIMIT" - ToolExecutionAttemptOutputErrorKindUpstreamRuntimeServerError ToolExecutionAttemptOutputErrorKind = "UPSTREAM_RUNTIME_SERVER_ERROR" - ToolExecutionAttemptOutputErrorKindUpstreamRuntimeUnmapped ToolExecutionAttemptOutputErrorKind = "UPSTREAM_RUNTIME_UNMAPPED" - ToolExecutionAttemptOutputErrorKindUnknown ToolExecutionAttemptOutputErrorKind = "UNKNOWN" + ToolExecutionAttemptOutputErrorKindToolkitLoadFailed ToolExecutionAttemptOutputErrorKind = "TOOLKIT_LOAD_FAILED" + ToolExecutionAttemptOutputErrorKindToolDefinitionBadDefinition ToolExecutionAttemptOutputErrorKind = "TOOL_DEFINITION_BAD_DEFINITION" + ToolExecutionAttemptOutputErrorKindToolDefinitionBadInputSchema ToolExecutionAttemptOutputErrorKind = "TOOL_DEFINITION_BAD_INPUT_SCHEMA" + ToolExecutionAttemptOutputErrorKindToolDefinitionBadOutputSchema ToolExecutionAttemptOutputErrorKind = "TOOL_DEFINITION_BAD_OUTPUT_SCHEMA" + ToolExecutionAttemptOutputErrorKindToolRequirementsNotMet ToolExecutionAttemptOutputErrorKind = "TOOL_REQUIREMENTS_NOT_MET" + ToolExecutionAttemptOutputErrorKindToolRuntimeBadInputValue ToolExecutionAttemptOutputErrorKind = "TOOL_RUNTIME_BAD_INPUT_VALUE" + ToolExecutionAttemptOutputErrorKindToolRuntimeBadOutputValue ToolExecutionAttemptOutputErrorKind = "TOOL_RUNTIME_BAD_OUTPUT_VALUE" + ToolExecutionAttemptOutputErrorKindToolRuntimeRetry ToolExecutionAttemptOutputErrorKind = "TOOL_RUNTIME_RETRY" + ToolExecutionAttemptOutputErrorKindToolRuntimeContextRequired ToolExecutionAttemptOutputErrorKind = "TOOL_RUNTIME_CONTEXT_REQUIRED" + ToolExecutionAttemptOutputErrorKindToolRuntimeFatal ToolExecutionAttemptOutputErrorKind = "TOOL_RUNTIME_FATAL" + ToolExecutionAttemptOutputErrorKindContextCheckFailed ToolExecutionAttemptOutputErrorKind = "CONTEXT_CHECK_FAILED" + ToolExecutionAttemptOutputErrorKindContextDenied ToolExecutionAttemptOutputErrorKind = "CONTEXT_DENIED" + ToolExecutionAttemptOutputErrorKindUpstreamRuntimeBadRequest ToolExecutionAttemptOutputErrorKind = "UPSTREAM_RUNTIME_BAD_REQUEST" + ToolExecutionAttemptOutputErrorKindUpstreamRuntimeAuthError ToolExecutionAttemptOutputErrorKind = "UPSTREAM_RUNTIME_AUTH_ERROR" + ToolExecutionAttemptOutputErrorKindUpstreamRuntimeNotFound ToolExecutionAttemptOutputErrorKind = "UPSTREAM_RUNTIME_NOT_FOUND" + ToolExecutionAttemptOutputErrorKindUpstreamRuntimeValidationError ToolExecutionAttemptOutputErrorKind = "UPSTREAM_RUNTIME_VALIDATION_ERROR" + ToolExecutionAttemptOutputErrorKindUpstreamRuntimeRateLimit ToolExecutionAttemptOutputErrorKind = "UPSTREAM_RUNTIME_RATE_LIMIT" + ToolExecutionAttemptOutputErrorKindUpstreamRuntimeServerError ToolExecutionAttemptOutputErrorKind = "UPSTREAM_RUNTIME_SERVER_ERROR" + ToolExecutionAttemptOutputErrorKindUpstreamRuntimeUnmapped ToolExecutionAttemptOutputErrorKind = "UPSTREAM_RUNTIME_UNMAPPED" + ToolExecutionAttemptOutputErrorKindNetworkTransportRuntimeTimeout ToolExecutionAttemptOutputErrorKind = "NETWORK_TRANSPORT_RUNTIME_TIMEOUT" + ToolExecutionAttemptOutputErrorKindNetworkTransportRuntimeUnreachable ToolExecutionAttemptOutputErrorKind = "NETWORK_TRANSPORT_RUNTIME_UNREACHABLE" + ToolExecutionAttemptOutputErrorKindNetworkTransportRuntimeUnmapped ToolExecutionAttemptOutputErrorKind = "NETWORK_TRANSPORT_RUNTIME_UNMAPPED" + ToolExecutionAttemptOutputErrorKindUnknown ToolExecutionAttemptOutputErrorKind = "UNKNOWN" ) func (r ToolExecutionAttemptOutputErrorKind) IsKnown() bool { switch r { - case ToolExecutionAttemptOutputErrorKindToolkitLoadFailed, ToolExecutionAttemptOutputErrorKindToolDefinitionBadDefinition, ToolExecutionAttemptOutputErrorKindToolDefinitionBadInputSchema, ToolExecutionAttemptOutputErrorKindToolDefinitionBadOutputSchema, ToolExecutionAttemptOutputErrorKindToolRequirementsNotMet, ToolExecutionAttemptOutputErrorKindToolRuntimeBadInputValue, ToolExecutionAttemptOutputErrorKindToolRuntimeBadOutputValue, ToolExecutionAttemptOutputErrorKindToolRuntimeRetry, ToolExecutionAttemptOutputErrorKindToolRuntimeContextRequired, ToolExecutionAttemptOutputErrorKindToolRuntimeFatal, ToolExecutionAttemptOutputErrorKindUpstreamRuntimeBadRequest, ToolExecutionAttemptOutputErrorKindUpstreamRuntimeAuthError, ToolExecutionAttemptOutputErrorKindUpstreamRuntimeNotFound, ToolExecutionAttemptOutputErrorKindUpstreamRuntimeValidationError, ToolExecutionAttemptOutputErrorKindUpstreamRuntimeRateLimit, ToolExecutionAttemptOutputErrorKindUpstreamRuntimeServerError, ToolExecutionAttemptOutputErrorKindUpstreamRuntimeUnmapped, ToolExecutionAttemptOutputErrorKindUnknown: + case ToolExecutionAttemptOutputErrorKindToolkitLoadFailed, ToolExecutionAttemptOutputErrorKindToolDefinitionBadDefinition, ToolExecutionAttemptOutputErrorKindToolDefinitionBadInputSchema, ToolExecutionAttemptOutputErrorKindToolDefinitionBadOutputSchema, ToolExecutionAttemptOutputErrorKindToolRequirementsNotMet, ToolExecutionAttemptOutputErrorKindToolRuntimeBadInputValue, ToolExecutionAttemptOutputErrorKindToolRuntimeBadOutputValue, ToolExecutionAttemptOutputErrorKindToolRuntimeRetry, ToolExecutionAttemptOutputErrorKindToolRuntimeContextRequired, ToolExecutionAttemptOutputErrorKindToolRuntimeFatal, ToolExecutionAttemptOutputErrorKindContextCheckFailed, ToolExecutionAttemptOutputErrorKindContextDenied, ToolExecutionAttemptOutputErrorKindUpstreamRuntimeBadRequest, ToolExecutionAttemptOutputErrorKindUpstreamRuntimeAuthError, ToolExecutionAttemptOutputErrorKindUpstreamRuntimeNotFound, ToolExecutionAttemptOutputErrorKindUpstreamRuntimeValidationError, ToolExecutionAttemptOutputErrorKindUpstreamRuntimeRateLimit, ToolExecutionAttemptOutputErrorKindUpstreamRuntimeServerError, ToolExecutionAttemptOutputErrorKindUpstreamRuntimeUnmapped, ToolExecutionAttemptOutputErrorKindNetworkTransportRuntimeTimeout, ToolExecutionAttemptOutputErrorKindNetworkTransportRuntimeUnreachable, ToolExecutionAttemptOutputErrorKindNetworkTransportRuntimeUnmapped, ToolExecutionAttemptOutputErrorKindUnknown: return true } return false } type ToolExecutionAttemptOutputLog struct { - Level string `json:"level,required"` - Message string `json:"message,required"` + Level string `json:"level" api:"required"` + Message string `json:"message" api:"required"` Subtype string `json:"subtype"` JSON toolExecutionAttemptOutputLogJSON `json:"-"` } @@ -755,7 +842,7 @@ func (r toolExecutionAttemptOutputLogJSON) RawJSON() string { } type ValueSchema struct { - ValType string `json:"val_type,required"` + ValType string `json:"val_type" api:"required"` Enum []string `json:"enum"` InnerValType string `json:"inner_val_type"` JSON valueSchemaJSON `json:"-"` @@ -779,6 +866,13 @@ func (r valueSchemaJSON) RawJSON() string { } type ToolListParams struct { + // JSON metadata filter. Array fields (service_domains, operations): shorthand + // array or object with any_of/all_of/none_of operators (case-insensitive). Boolean + // fields: read_only, destructive, idempotent, open_world. Extras: case-sensitive + // key-value subset match. + Filter param.Field[string] `query:"filter"` + // Include all versions of each tool + IncludeAllVersions param.Field[bool] `query:"include_all_versions"` // Comma separated tool formats that will be included in the response. IncludeFormat param.Field[[]ToolListParamsIncludeFormat] `query:"include_format"` // Number of items to return (default: 25, max: 100) @@ -816,7 +910,7 @@ func (r ToolListParamsIncludeFormat) IsKnown() bool { } type ToolAuthorizeParams struct { - AuthorizeToolRequest AuthorizeToolRequestParam `json:"authorize_tool_request,required"` + AuthorizeToolRequest AuthorizeToolRequestParam `json:"authorize_tool_request" api:"required"` } func (r ToolAuthorizeParams) MarshalJSON() (data []byte, err error) { @@ -824,7 +918,7 @@ func (r ToolAuthorizeParams) MarshalJSON() (data []byte, err error) { } type ToolExecuteParams struct { - ExecuteToolRequest ExecuteToolRequestParam `json:"execute_tool_request,required"` + ExecuteToolRequest ExecuteToolRequestParam `json:"execute_tool_request" api:"required"` } func (r ToolExecuteParams) MarshalJSON() (data []byte, err error) { diff --git a/tool_test.go b/tool_test.go index 06aa887..269325b 100644 --- a/tool_test.go +++ b/tool_test.go @@ -26,11 +26,13 @@ func TestToolListWithOptionalParams(t *testing.T) { option.WithAPIKey("My API Key"), ) _, err := client.Tools.List(context.TODO(), arcadego.ToolListParams{ - IncludeFormat: arcadego.F([]arcadego.ToolListParamsIncludeFormat{arcadego.ToolListParamsIncludeFormatArcade}), - Limit: arcadego.F(int64(0)), - Offset: arcadego.F(int64(0)), - Toolkit: arcadego.F("toolkit"), - UserID: arcadego.F("user_id"), + Filter: arcadego.F("filter"), + IncludeAllVersions: arcadego.F(true), + IncludeFormat: arcadego.F([]arcadego.ToolListParamsIncludeFormat{arcadego.ToolListParamsIncludeFormatArcade}), + Limit: arcadego.F(int64(0)), + Offset: arcadego.F(int64(0)), + Toolkit: arcadego.F("toolkit"), + UserID: arcadego.F("user_id"), }) if err != nil { var apierr *arcadego.Error diff --git a/toolformatted.go b/toolformatted.go index d280ee4..dc4ec04 100644 --- a/toolformatted.go +++ b/toolformatted.go @@ -66,20 +66,27 @@ func (r *ToolFormattedService) Get(ctx context.Context, name string, query ToolF opts = slices.Concat(r.Options, opts) if name == "" { err = errors.New("missing required name parameter") - return + return nil, err } path := fmt.Sprintf("v1/formatted_tools/%s", name) err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) - return + return res, err } -type ToolFormattedListResponse = interface{} +type ToolFormattedListResponse map[string]interface{} -type ToolFormattedGetResponse = interface{} +type ToolFormattedGetResponse map[string]interface{} type ToolFormattedListParams struct { + // JSON metadata filter. Array fields (service_domains, operations): shorthand + // array or object with any_of/all_of/none_of operators (case-insensitive). Boolean + // fields: read_only, destructive, idempotent, open_world. Extras: case-sensitive + // key-value subset match. + Filter param.Field[string] `query:"filter"` // Provider format Format param.Field[string] `query:"format"` + // Include all versions of each tool + IncludeAllVersions param.Field[bool] `query:"include_all_versions"` // Number of items to return (default: 25, max: 100) Limit param.Field[int64] `query:"limit"` // Offset from the start of the list (default: 0) diff --git a/toolformatted_test.go b/toolformatted_test.go index 4b6c16d..9c63ced 100644 --- a/toolformatted_test.go +++ b/toolformatted_test.go @@ -26,11 +26,13 @@ func TestToolFormattedListWithOptionalParams(t *testing.T) { option.WithAPIKey("My API Key"), ) _, err := client.Tools.Formatted.List(context.TODO(), arcadego.ToolFormattedListParams{ - Format: arcadego.F("format"), - Limit: arcadego.F(int64(0)), - Offset: arcadego.F(int64(0)), - Toolkit: arcadego.F("toolkit"), - UserID: arcadego.F("user_id"), + Filter: arcadego.F("filter"), + Format: arcadego.F("format"), + IncludeAllVersions: arcadego.F(true), + Limit: arcadego.F(int64(0)), + Offset: arcadego.F(int64(0)), + Toolkit: arcadego.F("toolkit"), + UserID: arcadego.F("user_id"), }) if err != nil { var apierr *arcadego.Error diff --git a/toolscheduled.go b/toolscheduled.go index 5471c0f..bfa78c3 100644 --- a/toolscheduled.go +++ b/toolscheduled.go @@ -65,11 +65,11 @@ func (r *ToolScheduledService) Get(ctx context.Context, id string, opts ...optio opts = slices.Concat(r.Options, opts) if id == "" { err = errors.New("missing required id parameter") - return + return nil, err } path := fmt.Sprintf("v1/scheduled_tools/%s", id) err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return + return res, err } type ToolScheduledGetResponse struct { diff --git a/worker.go b/worker.go index 9844f93..7a2437a 100644 --- a/worker.go +++ b/worker.go @@ -42,7 +42,7 @@ func (r *WorkerService) New(ctx context.Context, body WorkerNewParams, opts ...o opts = slices.Concat(r.Options, opts) path := "v1/workers" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + return res, err } // Update a worker @@ -50,11 +50,11 @@ func (r *WorkerService) Update(ctx context.Context, id string, body WorkerUpdate opts = slices.Concat(r.Options, opts) if id == "" { err = errors.New("missing required id parameter") - return + return nil, err } path := fmt.Sprintf("v1/workers/%s", id) err = requestconfig.ExecuteNewRequest(ctx, http.MethodPatch, path, body, &res, opts...) - return + return res, err } // List all workers with their definitions @@ -83,14 +83,14 @@ func (r *WorkerService) ListAutoPaging(ctx context.Context, query WorkerListPara // Delete a worker func (r *WorkerService) Delete(ctx context.Context, id string, opts ...option.RequestOption) (err error) { opts = slices.Concat(r.Options, opts) - opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...) + opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...) if id == "" { err = errors.New("missing required id parameter") - return + return err } path := fmt.Sprintf("v1/workers/%s", id) err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...) - return + return err } // Get a worker by ID @@ -98,11 +98,11 @@ func (r *WorkerService) Get(ctx context.Context, id string, opts ...option.Reque opts = slices.Concat(r.Options, opts) if id == "" { err = errors.New("missing required id parameter") - return + return nil, err } path := fmt.Sprintf("v1/workers/%s", id) err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return + return res, err } // Get the health of a worker @@ -110,11 +110,11 @@ func (r *WorkerService) Health(ctx context.Context, id string, opts ...option.Re opts = slices.Concat(r.Options, opts) if id == "" { err = errors.New("missing required id parameter") - return + return nil, err } path := fmt.Sprintf("v1/workers/%s/health", id) err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return + return res, err } // Returns a page of tools @@ -124,7 +124,7 @@ func (r *WorkerService) Tools(ctx context.Context, id string, query WorkerToolsP opts = append([]option.RequestOption{option.WithResponseInto(&raw)}, opts...) if id == "" { err = errors.New("missing required id parameter") - return + return nil, err } path := fmt.Sprintf("v1/workers/%s/tools", id) cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodGet, path, query, &res, opts...) @@ -145,7 +145,7 @@ func (r *WorkerService) ToolsAutoPaging(ctx context.Context, id string, query Wo } type CreateWorkerRequestParam struct { - ID param.Field[string] `json:"id,required"` + ID param.Field[string] `json:"id" api:"required"` Enabled param.Field[bool] `json:"enabled"` HTTP param.Field[CreateWorkerRequestHTTPParam] `json:"http"` Mcp param.Field[CreateWorkerRequestMcpParam] `json:"mcp"` @@ -157,10 +157,10 @@ func (r CreateWorkerRequestParam) MarshalJSON() (data []byte, err error) { } type CreateWorkerRequestHTTPParam struct { - Retry param.Field[int64] `json:"retry,required"` - Secret param.Field[string] `json:"secret,required"` - Timeout param.Field[int64] `json:"timeout,required"` - Uri param.Field[string] `json:"uri,required"` + Retry param.Field[int64] `json:"retry" api:"required"` + Secret param.Field[string] `json:"secret" api:"required"` + Timeout param.Field[int64] `json:"timeout" api:"required"` + Uri param.Field[string] `json:"uri" api:"required"` } func (r CreateWorkerRequestHTTPParam) MarshalJSON() (data []byte, err error) { @@ -168,9 +168,9 @@ func (r CreateWorkerRequestHTTPParam) MarshalJSON() (data []byte, err error) { } type CreateWorkerRequestMcpParam struct { - Retry param.Field[int64] `json:"retry,required"` - Timeout param.Field[int64] `json:"timeout,required"` - Uri param.Field[string] `json:"uri,required"` + Retry param.Field[int64] `json:"retry" api:"required"` + Timeout param.Field[int64] `json:"timeout" api:"required"` + Uri param.Field[string] `json:"uri" api:"required"` Headers param.Field[map[string]string] `json:"headers"` Oauth2 param.Field[CreateWorkerRequestMcpOauth2Param] `json:"oauth2"` Secrets param.Field[map[string]string] `json:"secrets"` @@ -367,7 +367,6 @@ type WorkerResponseHTTPSecret struct { Binding WorkerResponseHTTPSecretBinding `json:"binding"` Editable bool `json:"editable"` Exists bool `json:"exists"` - Hint string `json:"hint"` Value string `json:"value"` JSON workerResponseHTTPSecretJSON `json:"-"` } @@ -378,7 +377,6 @@ type workerResponseHTTPSecretJSON struct { Binding apijson.Field Editable apijson.Field Exists apijson.Field - Hint apijson.Field Value apijson.Field raw string ExtraFields map[string]apijson.Field @@ -410,20 +408,24 @@ func (r WorkerResponseHTTPSecretBinding) IsKnown() bool { } type WorkerResponseMcp struct { - Headers map[string]string `json:"headers"` - Oauth2 WorkerResponseMcpOauth2 `json:"oauth2"` - Retry int64 `json:"retry"` - Secrets map[string]WorkerResponseMcpSecret `json:"secrets"` - Timeout int64 `json:"timeout"` - Uri string `json:"uri"` - JSON workerResponseMcpJSON `json:"-"` + ExternalID string `json:"external_id"` + Headers map[string]string `json:"headers"` + Oauth2 WorkerResponseMcpOauth2 `json:"oauth2"` + RedirectUri string `json:"redirect_uri"` + Retry int64 `json:"retry"` + Secrets map[string]WorkerResponseMcpSecret `json:"secrets"` + Timeout int64 `json:"timeout"` + Uri string `json:"uri"` + JSON workerResponseMcpJSON `json:"-"` } // workerResponseMcpJSON contains the JSON metadata for the struct // [WorkerResponseMcp] type workerResponseMcpJSON struct { + ExternalID apijson.Field Headers apijson.Field Oauth2 apijson.Field + RedirectUri apijson.Field Retry apijson.Field Secrets apijson.Field Timeout apijson.Field @@ -444,7 +446,9 @@ type WorkerResponseMcpOauth2 struct { AuthorizationURL string `json:"authorization_url"` ClientID string `json:"client_id"` ClientSecret WorkerResponseMcpOauth2ClientSecret `json:"client_secret"` + ExternalID string `json:"external_id"` RedirectUri string `json:"redirect_uri"` + SupportedScopes []string `json:"supported_scopes"` JSON workerResponseMcpOauth2JSON `json:"-"` } @@ -454,7 +458,9 @@ type workerResponseMcpOauth2JSON struct { AuthorizationURL apijson.Field ClientID apijson.Field ClientSecret apijson.Field + ExternalID apijson.Field RedirectUri apijson.Field + SupportedScopes apijson.Field raw string ExtraFields map[string]apijson.Field } @@ -471,7 +477,6 @@ type WorkerResponseMcpOauth2ClientSecret struct { Binding WorkerResponseMcpOauth2ClientSecretBinding `json:"binding"` Editable bool `json:"editable"` Exists bool `json:"exists"` - Hint string `json:"hint"` Value string `json:"value"` JSON workerResponseMcpOauth2ClientSecretJSON `json:"-"` } @@ -482,7 +487,6 @@ type workerResponseMcpOauth2ClientSecretJSON struct { Binding apijson.Field Editable apijson.Field Exists apijson.Field - Hint apijson.Field Value apijson.Field raw string ExtraFields map[string]apijson.Field @@ -517,7 +521,6 @@ type WorkerResponseMcpSecret struct { Binding WorkerResponseMcpSecretsBinding `json:"binding"` Editable bool `json:"editable"` Exists bool `json:"exists"` - Hint string `json:"hint"` Value string `json:"value"` JSON workerResponseMcpSecretJSON `json:"-"` } @@ -528,7 +531,6 @@ type workerResponseMcpSecretJSON struct { Binding apijson.Field Editable apijson.Field Exists apijson.Field - Hint apijson.Field Value apijson.Field raw string ExtraFields map[string]apijson.Field @@ -643,7 +645,7 @@ func (r WorkerResponseType) IsKnown() bool { } type WorkerNewParams struct { - CreateWorkerRequest CreateWorkerRequestParam `json:"create_worker_request,required"` + CreateWorkerRequest CreateWorkerRequestParam `json:"create_worker_request" api:"required"` } func (r WorkerNewParams) MarshalJSON() (data []byte, err error) { @@ -651,7 +653,7 @@ func (r WorkerNewParams) MarshalJSON() (data []byte, err error) { } type WorkerUpdateParams struct { - UpdateWorkerRequest UpdateWorkerRequestParam `json:"update_worker_request,required"` + UpdateWorkerRequest UpdateWorkerRequestParam `json:"update_worker_request" api:"required"` } func (r WorkerUpdateParams) MarshalJSON() (data []byte, err error) { diff --git a/worker_test.go b/worker_test.go index e0d2c36..5f608dd 100644 --- a/worker_test.go +++ b/worker_test.go @@ -32,12 +32,12 @@ func TestWorkerNewWithOptionalParams(t *testing.T) { HTTP: arcadego.F(arcadego.CreateWorkerRequestHTTPParam{ Retry: arcadego.F(int64(0)), Secret: arcadego.F("secret"), - Timeout: arcadego.F(int64(1)), + Timeout: arcadego.F(int64(0)), Uri: arcadego.F("uri"), }), Mcp: arcadego.F(arcadego.CreateWorkerRequestMcpParam{ Retry: arcadego.F(int64(0)), - Timeout: arcadego.F(int64(1)), + Timeout: arcadego.F(int64(0)), Uri: arcadego.F("uri"), Headers: arcadego.F(map[string]string{ "foo": "string", @@ -85,7 +85,7 @@ func TestWorkerUpdateWithOptionalParams(t *testing.T) { HTTP: arcadego.F(arcadego.UpdateWorkerRequestHTTPParam{ Retry: arcadego.F(int64(0)), Secret: arcadego.F("secret"), - Timeout: arcadego.F(int64(1)), + Timeout: arcadego.F(int64(0)), Uri: arcadego.F("uri"), }), Mcp: arcadego.F(arcadego.UpdateWorkerRequestMcpParam{ @@ -101,7 +101,7 @@ func TestWorkerUpdateWithOptionalParams(t *testing.T) { Secrets: arcadego.F(map[string]string{ "foo": "string", }), - Timeout: arcadego.F(int64(1)), + Timeout: arcadego.F(int64(0)), Uri: arcadego.F("uri"), }), },