diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 1aa883d..8587ff4 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -5,19 +5,18 @@ RUN apt-get update && apt-get install -y \ libxkbcommon0 \ ca-certificates \ git \ - golang \ unzip \ libc++1 \ vim \ + curl \ + procps \ && apt-get clean autoclean +RUN curl -OL https://go.dev/dl/go1.24.0.linux-amd64.tar.gz && \ + tar -C /usr/local -xzvf go1.24.0.linux-amd64.tar.gz && \ + rm go1.24.0.linux-amd64.tar.gz +ENV PATH="$PATH:/usr/local/go/bin" + # Ensure UTF-8 encoding ENV LANG=C.UTF-8 ENV LC_ALL=C.UTF-8 - -ENV GOPATH=/go -ENV PATH=$GOPATH/bin:$PATH - -WORKDIR /workspace - -COPY . /workspace diff --git a/.github/workflows/create-releases.yml b/.github/workflows/create-releases.yml deleted file mode 100644 index d15f4f6..0000000 --- a/.github/workflows/create-releases.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Create releases -on: - schedule: - - cron: '0 5 * * *' # every day at 5am UTC - push: - branches: - - main - -jobs: - release: - name: release - if: github.ref == 'refs/heads/main' && github.repository == 'openlayer-ai/openlayer-go' - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - uses: stainless-api/trigger-release-please@v1 - id: release - with: - repo: ${{ github.event.repository.full_name }} - stainless-api-key: ${{ secrets.STAINLESS_API_KEY }} - - - name: Generate godocs - if: ${{ steps.release.outputs.releases_created }} - run: | - version=$(jq -r '. | to_entries[0] | .value' .release-please-manifest.json) - curl -X POST https://pkg.go.dev/fetch/github.com/openlayer-ai/openlayer-go@v${version} diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 000572e..b069996 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.13" + ".": "0.1.0-alpha.14" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index dd47305..c254947 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 14 +configured_endpoints: 15 diff --git a/CHANGELOG.md b/CHANGELOG.md index 454a964..bb0ec58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ # Changelog +## 0.1.0-alpha.14 (2025-03-13) + +Full Changelog: [v0.1.0-alpha.13...v0.1.0-alpha.14](https://github.com/openlayer-ai/openlayer-go/compare/v0.1.0-alpha.13...v0.1.0-alpha.14) + +### Features + +* **api:** add endpoint to retrieve commit by id ([#64](https://github.com/openlayer-ai/openlayer-go/issues/64)) ([201cb4a](https://github.com/openlayer-ai/openlayer-go/commit/201cb4acd0e572cecb46ffdc29979c38a52846fb)) +* **api:** api update ([1d5c8e7](https://github.com/openlayer-ai/openlayer-go/commit/1d5c8e7f9a98ab806517fbd7c2ac8c6ac456f04d)) +* **client:** send `X-Stainless-Timeout` header ([d9948ef](https://github.com/openlayer-ai/openlayer-go/commit/d9948ef4b8b56ed71ddd2ea0f4519bf40dc5d52f)) + + +### Bug Fixes + +* **client:** don't truncate manually specified filenames ([f7b96d9](https://github.com/openlayer-ai/openlayer-go/commit/f7b96d9ebe96937df548fe0797fbbaf8ba77109a)) +* do not call path.Base on ContentType ([0b98d8e](https://github.com/openlayer-ai/openlayer-go/commit/0b98d8e38cb8716cc61ee62f9aa4b58250a4b581)) +* fix early cancel when RequestTimeout is provided for streaming requests ([f0c9a39](https://github.com/openlayer-ai/openlayer-go/commit/f0c9a39e93d5aab9a2f78b95f1a6ee0aa27632ca)) +* fix interface implementation stub names for unions ([0d8c74f](https://github.com/openlayer-ai/openlayer-go/commit/0d8c74f4d02b5d154357d98e892ac074470b474b)) +* fix unicode encoding for json ([cdaee21](https://github.com/openlayer-ai/openlayer-go/commit/cdaee2125cb3c27f1a94cefc70d87ad5e12b79cc)) + + +### Chores + +* add UnionUnmarshaler for responses that are interfaces ([3295c9b](https://github.com/openlayer-ai/openlayer-go/commit/3295c9bcc892bf7db4f1068120be3a86b86b1281)) +* **internal:** codegen related update ([57b756f](https://github.com/openlayer-ai/openlayer-go/commit/57b756f1e31665115bc2cac4d7527f21965376ad)) +* **internal:** codegen related update ([c2a18b7](https://github.com/openlayer-ai/openlayer-go/commit/c2a18b7d8ebe2df5f23428be410172a0dbd330cd)) +* **internal:** codegen related update ([5582642](https://github.com/openlayer-ai/openlayer-go/commit/5582642f1d2b958566a842f4c9663baea39563cc)) +* **internal:** fix devcontainers setup ([1d42cd7](https://github.com/openlayer-ai/openlayer-go/commit/1d42cd7adbc487450809a3da28cdb47cc11da3db)) +* refactor client tests ([d2732fe](https://github.com/openlayer-ai/openlayer-go/commit/d2732fee321e2f829eafa4374c0c093d6e378f72)) + + +### Documentation + +* document raw responses ([1c66d2a](https://github.com/openlayer-ai/openlayer-go/commit/1c66d2a1e698e42799d142ff57ad1b2a51630b98)) + ## 0.1.0-alpha.13 (2025-01-02) Full Changelog: [v0.1.0-alpha.12...v0.1.0-alpha.13](https://github.com/openlayer-ai/openlayer-go/compare/v0.1.0-alpha.12...v0.1.0-alpha.13) diff --git a/README.md b/README.md index a94ee33..9146ee8 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Or to pin the version: ```sh -go get -u 'github.com/openlayer-ai/openlayer-go@v0.1.0-alpha.13' +go get -u 'github.com/openlayer-ai/openlayer-go@v0.1.0-alpha.14' ``` @@ -66,9 +66,9 @@ func main() { Rows: openlayer.F([]map[string]interface{}{{ "user_query": "what is the meaning of life?", "output": "42", - "tokens": map[string]interface{}{}, - "cost": map[string]interface{}{}, - "timestamp": map[string]interface{}{}, + "tokens": 7, + "cost": 0.02, + "timestamp": 1610000000, }}), }, ) @@ -207,9 +207,9 @@ _, err := client.InferencePipelines.Data.Stream( Rows: openlayer.F([]map[string]interface{}{{ "user_query": "what is the meaning of life?", "output": "42", - "tokens": map[string]interface{}{}, - "cost": map[string]interface{}{}, - "timestamp": map[string]interface{}{}, + "tokens": 7, + "cost": 0.02, + "timestamp": 1610000000, }}), }, ) @@ -251,9 +251,9 @@ client.InferencePipelines.Data.Stream( Rows: openlayer.F([]map[string]interface{}{{ "user_query": "what is the meaning of life?", "output": "42", - "tokens": map[string]interface{}{}, - "cost": map[string]interface{}{}, - "timestamp": map[string]interface{}{}, + "tokens": 7, + "cost": 0.02, + "timestamp": 1610000000, }}), }, // This sets the per-retry timeout @@ -303,15 +303,53 @@ client.InferencePipelines.Data.Stream( Rows: openlayer.F([]map[string]interface{}{{ "user_query": "what is the meaning of life?", "output": "42", - "tokens": map[string]interface{}{}, - "cost": map[string]interface{}{}, - "timestamp": map[string]interface{}{}, + "tokens": 7, + "cost": 0.02, + "timestamp": 1610000000, }}), }, option.WithMaxRetries(5), ) ``` +### Accessing raw response data (e.g. response headers) + +You can access the raw HTTP response data by using the `option.WithResponseInto()` request option. This is useful when +you need to examine response headers, status codes, or other details. + +```go +// Create a variable to store the HTTP response +var response *http.Response +response, err := client.InferencePipelines.Data.Stream( + context.TODO(), + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + openlayer.InferencePipelineDataStreamParams{ + Config: openlayer.F[openlayer.InferencePipelineDataStreamParamsConfigUnion](openlayer.InferencePipelineDataStreamParamsConfigLlmData{ + InputVariableNames: openlayer.F([]string{"user_query"}), + OutputColumnName: openlayer.F("output"), + NumOfTokenColumnName: openlayer.F("tokens"), + CostColumnName: openlayer.F("cost"), + TimestampColumnName: openlayer.F("timestamp"), + }), + Rows: openlayer.F([]map[string]interface{}{{ + "user_query": "what is the meaning of life?", + "output": "42", + "tokens": 7, + "cost": 0.02, + "timestamp": 1610000000, + }}), + }, + option.WithResponseInto(&response), +) +if err != nil { + // handle error +} +fmt.Printf("%+v\n", response) + +fmt.Printf("Status Code: %d\n", response.StatusCode) +fmt.Printf("Headers: %+#v\n", response.Header) +``` + ### Making custom/undocumented requests This library is typed for convenient access to the documented API. If you need to access undocumented @@ -402,7 +440,7 @@ middleware has been applied. This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: -1. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals)_. +1. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_ 2. Changes that we do not expect to impact the vast majority of users in practice. We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. diff --git a/api.md b/api.md index 1e30d91..bf0898d 100644 --- a/api.md +++ b/api.md @@ -36,6 +36,14 @@ Methods: # Commits +Response Types: + +- openlayer.CommitGetResponse + +Methods: + +- client.Commits.Get(ctx context.Context, projectVersionID string) (openlayer.CommitGetResponse, error) + ## TestResults Response Types: diff --git a/client_test.go b/client_test.go index 4370936..e498ab9 100644 --- a/client_test.go +++ b/client_test.go @@ -51,9 +51,9 @@ func TestUserAgentHeader(t *testing.T) { Rows: openlayer.F([]map[string]interface{}{{ "user_query": "what is the meaning of life?", "output": "42", - "tokens": map[string]interface{}{}, - "cost": map[string]interface{}{}, - "timestamp": map[string]interface{}{}, + "tokens": 7, + "cost": 0.02, + "timestamp": 1610000000, }}), }, ) @@ -79,7 +79,7 @@ func TestRetryAfter(t *testing.T) { }, }), ) - res, err := client.InferencePipelines.Data.Stream( + _, err := client.InferencePipelines.Data.Stream( context.Background(), "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", openlayer.InferencePipelineDataStreamParams{ @@ -93,14 +93,14 @@ func TestRetryAfter(t *testing.T) { Rows: openlayer.F([]map[string]interface{}{{ "user_query": "what is the meaning of life?", "output": "42", - "tokens": map[string]interface{}{}, - "cost": map[string]interface{}{}, - "timestamp": map[string]interface{}{}, + "tokens": 7, + "cost": 0.02, + "timestamp": 1610000000, }}), }, ) - if err == nil || res != nil { - t.Error("Expected there to be a cancel error and for the response to be nil") + if err == nil { + t.Error("Expected there to be a cancel error") } attempts := len(retryCountHeaders) @@ -132,7 +132,7 @@ func TestDeleteRetryCountHeader(t *testing.T) { }), option.WithHeaderDel("X-Stainless-Retry-Count"), ) - res, err := client.InferencePipelines.Data.Stream( + _, err := client.InferencePipelines.Data.Stream( context.Background(), "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", openlayer.InferencePipelineDataStreamParams{ @@ -146,14 +146,14 @@ func TestDeleteRetryCountHeader(t *testing.T) { Rows: openlayer.F([]map[string]interface{}{{ "user_query": "what is the meaning of life?", "output": "42", - "tokens": map[string]interface{}{}, - "cost": map[string]interface{}{}, - "timestamp": map[string]interface{}{}, + "tokens": 7, + "cost": 0.02, + "timestamp": 1610000000, }}), }, ) - if err == nil || res != nil { - t.Error("Expected there to be a cancel error and for the response to be nil") + if err == nil { + t.Error("Expected there to be a cancel error") } expectedRetryCountHeaders := []string{"", "", ""} @@ -180,7 +180,7 @@ func TestOverwriteRetryCountHeader(t *testing.T) { }), option.WithHeader("X-Stainless-Retry-Count", "42"), ) - res, err := client.InferencePipelines.Data.Stream( + _, err := client.InferencePipelines.Data.Stream( context.Background(), "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", openlayer.InferencePipelineDataStreamParams{ @@ -194,14 +194,14 @@ func TestOverwriteRetryCountHeader(t *testing.T) { Rows: openlayer.F([]map[string]interface{}{{ "user_query": "what is the meaning of life?", "output": "42", - "tokens": map[string]interface{}{}, - "cost": map[string]interface{}{}, - "timestamp": map[string]interface{}{}, + "tokens": 7, + "cost": 0.02, + "timestamp": 1610000000, }}), }, ) - if err == nil || res != nil { - t.Error("Expected there to be a cancel error and for the response to be nil") + if err == nil { + t.Error("Expected there to be a cancel error") } expectedRetryCountHeaders := []string{"42", "42", "42"} @@ -227,7 +227,7 @@ func TestRetryAfterMs(t *testing.T) { }, }), ) - res, err := client.InferencePipelines.Data.Stream( + _, err := client.InferencePipelines.Data.Stream( context.Background(), "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", openlayer.InferencePipelineDataStreamParams{ @@ -241,14 +241,14 @@ func TestRetryAfterMs(t *testing.T) { Rows: openlayer.F([]map[string]interface{}{{ "user_query": "what is the meaning of life?", "output": "42", - "tokens": map[string]interface{}{}, - "cost": map[string]interface{}{}, - "timestamp": map[string]interface{}{}, + "tokens": 7, + "cost": 0.02, + "timestamp": 1610000000, }}), }, ) - if err == nil || res != nil { - t.Error("Expected there to be a cancel error and for the response to be nil") + if err == nil { + t.Error("Expected there to be a cancel error") } if want := 3; attempts != want { t.Errorf("Expected %d attempts, got %d", want, attempts) @@ -268,7 +268,7 @@ func TestContextCancel(t *testing.T) { ) cancelCtx, cancel := context.WithCancel(context.Background()) cancel() - res, err := client.InferencePipelines.Data.Stream( + _, err := client.InferencePipelines.Data.Stream( cancelCtx, "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", openlayer.InferencePipelineDataStreamParams{ @@ -282,14 +282,14 @@ func TestContextCancel(t *testing.T) { Rows: openlayer.F([]map[string]interface{}{{ "user_query": "what is the meaning of life?", "output": "42", - "tokens": map[string]interface{}{}, - "cost": map[string]interface{}{}, - "timestamp": map[string]interface{}{}, + "tokens": 7, + "cost": 0.02, + "timestamp": 1610000000, }}), }, ) - if err == nil || res != nil { - t.Error("Expected there to be a cancel error and for the response to be nil") + if err == nil { + t.Error("Expected there to be a cancel error") } } @@ -306,7 +306,7 @@ func TestContextCancelDelay(t *testing.T) { ) cancelCtx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) defer cancel() - res, err := client.InferencePipelines.Data.Stream( + _, err := client.InferencePipelines.Data.Stream( cancelCtx, "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", openlayer.InferencePipelineDataStreamParams{ @@ -320,14 +320,14 @@ func TestContextCancelDelay(t *testing.T) { Rows: openlayer.F([]map[string]interface{}{{ "user_query": "what is the meaning of life?", "output": "42", - "tokens": map[string]interface{}{}, - "cost": map[string]interface{}{}, - "timestamp": map[string]interface{}{}, + "tokens": 7, + "cost": 0.02, + "timestamp": 1610000000, }}), }, ) - if err == nil || res != nil { - t.Error("expected there to be a cancel error and for the response to be nil") + if err == nil { + t.Error("expected there to be a cancel error") } } @@ -350,7 +350,7 @@ func TestContextDeadline(t *testing.T) { }, }), ) - res, err := client.InferencePipelines.Data.Stream( + _, err := client.InferencePipelines.Data.Stream( deadlineCtx, "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", openlayer.InferencePipelineDataStreamParams{ @@ -364,14 +364,14 @@ func TestContextDeadline(t *testing.T) { Rows: openlayer.F([]map[string]interface{}{{ "user_query": "what is the meaning of life?", "output": "42", - "tokens": map[string]interface{}{}, - "cost": map[string]interface{}{}, - "timestamp": map[string]interface{}{}, + "tokens": 7, + "cost": 0.02, + "timestamp": 1610000000, }}), }, ) - if err == nil || res != nil { - t.Error("expected there to be a deadline error and for the response to be nil") + if err == nil { + t.Error("expected there to be a deadline error") } close(testDone) }() diff --git a/commit.go b/commit.go index 651371a..9bcda00 100644 --- a/commit.go +++ b/commit.go @@ -3,6 +3,14 @@ package openlayer import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/openlayer-ai/openlayer-go/internal/apijson" + "github.com/openlayer-ai/openlayer-go/internal/requestconfig" "github.com/openlayer-ai/openlayer-go/option" ) @@ -26,3 +34,180 @@ func NewCommitService(opts ...option.RequestOption) (r *CommitService) { r.TestResults = NewCommitTestResultService(opts...) return } + +// Retrieve a project version (commit) by its id. +func (r *CommitService) Get(ctx context.Context, projectVersionID string, opts ...option.RequestOption) (res *CommitGetResponse, err error) { + opts = append(r.Options[:], opts...) + if projectVersionID == "" { + err = errors.New("missing required projectVersionId parameter") + return + } + path := fmt.Sprintf("versions/%s", projectVersionID) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + return +} + +type CommitGetResponse struct { + // The project version (commit) id. + ID string `json:"id,required" format:"uuid"` + // The details of a commit (project version). + Commit CommitGetResponseCommit `json:"commit,required"` + // The commit archive date. + DateArchived time.Time `json:"dateArchived,required,nullable" format:"date-time"` + // The project version (commit) creation date. + DateCreated time.Time `json:"dateCreated,required" format:"date-time"` + // The number of tests that are failing for the commit. + FailingGoalCount int64 `json:"failingGoalCount,required"` + // The model id. + MlModelID string `json:"mlModelId,required,nullable" format:"uuid"` + // The number of tests that are passing for the commit. + PassingGoalCount int64 `json:"passingGoalCount,required"` + // The project id. + ProjectID string `json:"projectId,required" format:"uuid"` + // The commit status. Initially, the commit is `queued`, then, it switches to + // `running`. Finally, it can be `paused`, `failed`, or `completed`. + Status CommitGetResponseStatus `json:"status,required"` + // The commit status message. + StatusMessage string `json:"statusMessage,required,nullable"` + // The total number of tests for the commit. + TotalGoalCount int64 `json:"totalGoalCount,required"` + // The training dataset id. + TrainingDatasetID string `json:"trainingDatasetId,required,nullable" format:"uuid"` + // The validation dataset id. + ValidationDatasetID string `json:"validationDatasetId,required,nullable" format:"uuid"` + // Whether the commit is archived. + Archived bool `json:"archived,nullable"` + // The deployment status associated with the commit's model. + DeploymentStatus string `json:"deploymentStatus"` + Links CommitGetResponseLinks `json:"links"` + JSON commitGetResponseJSON `json:"-"` +} + +// commitGetResponseJSON contains the JSON metadata for the struct +// [CommitGetResponse] +type commitGetResponseJSON struct { + ID apijson.Field + Commit apijson.Field + DateArchived apijson.Field + DateCreated apijson.Field + FailingGoalCount apijson.Field + MlModelID apijson.Field + PassingGoalCount apijson.Field + ProjectID apijson.Field + Status apijson.Field + StatusMessage apijson.Field + TotalGoalCount apijson.Field + TrainingDatasetID apijson.Field + ValidationDatasetID apijson.Field + Archived apijson.Field + DeploymentStatus apijson.Field + Links apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *CommitGetResponse) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r commitGetResponseJSON) RawJSON() string { + return r.raw +} + +// The details of a commit (project version). +type CommitGetResponseCommit struct { + // The commit id. + ID string `json:"id,required" format:"uuid"` + // The author id of the commit. + AuthorID string `json:"authorId,required" format:"uuid"` + // The size of the commit bundle in bytes. + FileSize int64 `json:"fileSize,required,nullable"` + // The commit message. + Message string `json:"message,required"` + // The model id. + MlModelID string `json:"mlModelId,required,nullable" format:"uuid"` + // The storage URI where the commit bundle is stored. + StorageUri string `json:"storageUri,required"` + // The training dataset id. + TrainingDatasetID string `json:"trainingDatasetId,required,nullable" format:"uuid"` + // The validation dataset id. + ValidationDatasetID string `json:"validationDatasetId,required,nullable" format:"uuid"` + // The commit creation date. + DateCreated time.Time `json:"dateCreated" format:"date-time"` + // The ref of the corresponding git commit. + GitCommitRef string `json:"gitCommitRef"` + // The SHA of the corresponding git commit. + GitCommitSha int64 `json:"gitCommitSha"` + // The URL of the corresponding git commit. + GitCommitURL string `json:"gitCommitUrl"` + JSON commitGetResponseCommitJSON `json:"-"` +} + +// commitGetResponseCommitJSON contains the JSON metadata for the struct +// [CommitGetResponseCommit] +type commitGetResponseCommitJSON struct { + ID apijson.Field + AuthorID apijson.Field + FileSize apijson.Field + Message apijson.Field + MlModelID apijson.Field + StorageUri apijson.Field + TrainingDatasetID apijson.Field + ValidationDatasetID apijson.Field + DateCreated apijson.Field + GitCommitRef apijson.Field + GitCommitSha apijson.Field + GitCommitURL apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *CommitGetResponseCommit) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r commitGetResponseCommitJSON) RawJSON() string { + return r.raw +} + +// The commit status. Initially, the commit is `queued`, then, it switches to +// `running`. Finally, it can be `paused`, `failed`, or `completed`. +type CommitGetResponseStatus string + +const ( + CommitGetResponseStatusQueued CommitGetResponseStatus = "queued" + CommitGetResponseStatusRunning CommitGetResponseStatus = "running" + CommitGetResponseStatusPaused CommitGetResponseStatus = "paused" + CommitGetResponseStatusFailed CommitGetResponseStatus = "failed" + CommitGetResponseStatusCompleted CommitGetResponseStatus = "completed" + CommitGetResponseStatusUnknown CommitGetResponseStatus = "unknown" +) + +func (r CommitGetResponseStatus) IsKnown() bool { + switch r { + case CommitGetResponseStatusQueued, CommitGetResponseStatusRunning, CommitGetResponseStatusPaused, CommitGetResponseStatusFailed, CommitGetResponseStatusCompleted, CommitGetResponseStatusUnknown: + return true + } + return false +} + +type CommitGetResponseLinks struct { + App string `json:"app,required"` + JSON commitGetResponseLinksJSON `json:"-"` +} + +// commitGetResponseLinksJSON contains the JSON metadata for the struct +// [CommitGetResponseLinks] +type commitGetResponseLinksJSON struct { + App apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *CommitGetResponseLinks) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r commitGetResponseLinksJSON) RawJSON() string { + return r.raw +} diff --git a/commit_test.go b/commit_test.go new file mode 100644 index 0000000..15bb08b --- /dev/null +++ b/commit_test.go @@ -0,0 +1,36 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package openlayer_test + +import ( + "context" + "errors" + "os" + "testing" + + "github.com/openlayer-ai/openlayer-go" + "github.com/openlayer-ai/openlayer-go/internal/testutil" + "github.com/openlayer-ai/openlayer-go/option" +) + +func TestCommitGet(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 := openlayer.NewClient( + option.WithBaseURL(baseURL), + option.WithAPIKey("My API Key"), + ) + _, err := client.Commits.Get(context.TODO(), "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + if err != nil { + var apierr *openlayer.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} diff --git a/field.go b/field.go index 388bfca..a55d709 100644 --- a/field.go +++ b/field.go @@ -46,5 +46,5 @@ type file struct { contentType string } -func (f *file) Name() string { return f.name } func (f *file) ContentType() string { return f.contentType } +func (f *file) Filename() string { return f.name } diff --git a/inferencepipeline.go b/inferencepipeline.go index 38c6671..76d85c1 100644 --- a/inferencepipeline.go +++ b/inferencepipeline.go @@ -357,22 +357,34 @@ func (r inferencePipelineGetResponseProjectGitRepoJSON) RawJSON() string { } type InferencePipelineGetResponseWorkspace struct { - ID string `json:"id,required" format:"uuid"` - CreatorID string `json:"creatorId,required,nullable" format:"uuid"` - DateCreated time.Time `json:"dateCreated,required" format:"date-time"` - DateUpdated time.Time `json:"dateUpdated,required" format:"date-time"` - InviteCount int64 `json:"inviteCount,required"` - MemberCount int64 `json:"memberCount,required"` - Name string `json:"name,required"` - PeriodEndDate time.Time `json:"periodEndDate,required,nullable" format:"date-time"` - PeriodStartDate time.Time `json:"periodStartDate,required,nullable" format:"date-time"` - ProjectCount int64 `json:"projectCount,required"` - Slug string `json:"slug,required"` - Status InferencePipelineGetResponseWorkspaceStatus `json:"status,required"` - MonthlyUsage []InferencePipelineGetResponseWorkspaceMonthlyUsage `json:"monthlyUsage"` - SAMLOnlyAccess bool `json:"samlOnlyAccess"` - WildcardDomains []string `json:"wildcardDomains"` - JSON inferencePipelineGetResponseWorkspaceJSON `json:"-"` + // The workspace id. + ID string `json:"id,required" format:"uuid"` + // The workspace creator id. + CreatorID string `json:"creatorId,required,nullable" format:"uuid"` + // The workspace creation date. + DateCreated time.Time `json:"dateCreated,required" format:"date-time"` + // The workspace last updated date. + DateUpdated time.Time `json:"dateUpdated,required" format:"date-time"` + // The number of invites in the workspace. + InviteCount int64 `json:"inviteCount,required"` + // The number of members in the workspace. + MemberCount int64 `json:"memberCount,required"` + // The workspace name. + Name string `json:"name,required"` + // The end date of the current billing period. + PeriodEndDate time.Time `json:"periodEndDate,required,nullable" format:"date-time"` + // The start date of the current billing period. + PeriodStartDate time.Time `json:"periodStartDate,required,nullable" format:"date-time"` + // The number of projects in the workspace. + ProjectCount int64 `json:"projectCount,required"` + // The workspace slug. + Slug string `json:"slug,required"` + Status InferencePipelineGetResponseWorkspaceStatus `json:"status,required"` + MonthlyUsage []InferencePipelineGetResponseWorkspaceMonthlyUsage `json:"monthlyUsage"` + // Whether the workspace only allows SAML authentication. + SAMLOnlyAccess bool `json:"samlOnlyAccess"` + WildcardDomains []string `json:"wildcardDomains"` + JSON inferencePipelineGetResponseWorkspaceJSON `json:"-"` } // inferencePipelineGetResponseWorkspaceJSON contains the JSON metadata for the @@ -729,22 +741,34 @@ func (r inferencePipelineUpdateResponseProjectGitRepoJSON) RawJSON() string { } type InferencePipelineUpdateResponseWorkspace struct { - ID string `json:"id,required" format:"uuid"` - CreatorID string `json:"creatorId,required,nullable" format:"uuid"` - DateCreated time.Time `json:"dateCreated,required" format:"date-time"` - DateUpdated time.Time `json:"dateUpdated,required" format:"date-time"` - InviteCount int64 `json:"inviteCount,required"` - MemberCount int64 `json:"memberCount,required"` - Name string `json:"name,required"` - PeriodEndDate time.Time `json:"periodEndDate,required,nullable" format:"date-time"` - PeriodStartDate time.Time `json:"periodStartDate,required,nullable" format:"date-time"` - ProjectCount int64 `json:"projectCount,required"` - Slug string `json:"slug,required"` - Status InferencePipelineUpdateResponseWorkspaceStatus `json:"status,required"` - MonthlyUsage []InferencePipelineUpdateResponseWorkspaceMonthlyUsage `json:"monthlyUsage"` - SAMLOnlyAccess bool `json:"samlOnlyAccess"` - WildcardDomains []string `json:"wildcardDomains"` - JSON inferencePipelineUpdateResponseWorkspaceJSON `json:"-"` + // The workspace id. + ID string `json:"id,required" format:"uuid"` + // The workspace creator id. + CreatorID string `json:"creatorId,required,nullable" format:"uuid"` + // The workspace creation date. + DateCreated time.Time `json:"dateCreated,required" format:"date-time"` + // The workspace last updated date. + DateUpdated time.Time `json:"dateUpdated,required" format:"date-time"` + // The number of invites in the workspace. + InviteCount int64 `json:"inviteCount,required"` + // The number of members in the workspace. + MemberCount int64 `json:"memberCount,required"` + // The workspace name. + Name string `json:"name,required"` + // The end date of the current billing period. + PeriodEndDate time.Time `json:"periodEndDate,required,nullable" format:"date-time"` + // The start date of the current billing period. + PeriodStartDate time.Time `json:"periodStartDate,required,nullable" format:"date-time"` + // The number of projects in the workspace. + ProjectCount int64 `json:"projectCount,required"` + // The workspace slug. + Slug string `json:"slug,required"` + Status InferencePipelineUpdateResponseWorkspaceStatus `json:"status,required"` + MonthlyUsage []InferencePipelineUpdateResponseWorkspaceMonthlyUsage `json:"monthlyUsage"` + // Whether the workspace only allows SAML authentication. + SAMLOnlyAccess bool `json:"samlOnlyAccess"` + WildcardDomains []string `json:"wildcardDomains"` + JSON inferencePipelineUpdateResponseWorkspaceJSON `json:"-"` } // inferencePipelineUpdateResponseWorkspaceJSON contains the JSON metadata for the diff --git a/internal/apiform/encoder.go b/internal/apiform/encoder.go index baaa704..6fad127 100644 --- a/internal/apiform/encoder.go +++ b/internal/apiform/encoder.go @@ -315,11 +315,13 @@ func (e *encoder) newReaderTypeEncoder() encoderFunc { reader := value.Convert(reflect.TypeOf((*io.Reader)(nil)).Elem()).Interface().(io.Reader) filename := "anonymous_file" contentType := "application/octet-stream" - if named, ok := reader.(interface{ Name() string }); ok { + if named, ok := reader.(interface{ Filename() string }); ok { + filename = named.Filename() + } else if named, ok := reader.(interface{ Name() string }); ok { filename = path.Base(named.Name()) } if typed, ok := reader.(interface{ ContentType() string }); ok { - contentType = path.Base(typed.ContentType()) + contentType = typed.ContentType() } // Below is taken almost 1-for-1 from [multipart.CreateFormFile] diff --git a/internal/apijson/encoder.go b/internal/apijson/encoder.go index 6577f2d..a31fcd4 100644 --- a/internal/apijson/encoder.go +++ b/internal/apijson/encoder.go @@ -143,7 +143,7 @@ func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc { // code more and this current code shouldn't cause any issues case reflect.String: return func(v reflect.Value) ([]byte, error) { - return []byte(fmt.Sprintf("%q", v.String())), nil + return json.Marshal(v.Interface()) } case reflect.Bool: return func(v reflect.Value) ([]byte, error) { diff --git a/internal/apijson/port.go b/internal/apijson/port.go index 80b323b..502ab77 100644 --- a/internal/apijson/port.go +++ b/internal/apijson/port.go @@ -34,22 +34,35 @@ func Port(from any, to any) error { fromJSON := fromVal.FieldByName("JSON") toJSON := toVal.FieldByName("JSON") - // First, iterate through the from fields and load all the "normal" fields in the struct to the map of - // string to reflect.Value, as well as their raw .JSON.Foo counterpart. - for i := 0; i < fromType.NumField(); i++ { - field := fromType.Field(i) - ptag, ok := parseJSONStructTag(field) - if !ok { - continue - } - if ptag.name == "-" { - continue + // Iterate through the fields of v and load all the "normal" fields in the struct to the map of + // string to reflect.Value, as well as their raw .JSON.Foo counterpart indicated by j. + var getFields func(t reflect.Type, v reflect.Value) + getFields = func(t reflect.Type, v reflect.Value) { + j := v.FieldByName("JSON") + + // Recurse into anonymous fields first, since the fields on the object should win over the fields in the + // embedded object. + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + if field.Anonymous { + getFields(field.Type, v.Field(i)) + continue + } } - values[ptag.name] = fromVal.Field(i) - if fromJSON.IsValid() { - fields[ptag.name] = fromJSON.FieldByName(field.Name) + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + ptag, ok := parseJSONStructTag(field) + if !ok || ptag.name == "-" { + continue + } + values[ptag.name] = v.Field(i) + if j.IsValid() { + fields[ptag.name] = j.FieldByName(field.Name) + } } } + getFields(fromType, fromVal) // Use the values from the previous step to populate the 'to' struct. for i := 0; i < toType.NumField(); i++ { diff --git a/internal/apijson/port_test.go b/internal/apijson/port_test.go index f9b6e3f..1154053 100644 --- a/internal/apijson/port_test.go +++ b/internal/apijson/port_test.go @@ -94,6 +94,39 @@ type CardMastercardData struct { Bar int64 `json:"bar"` } +type CommonFields struct { + Metadata Metadata `json:"metadata"` + Value string `json:"value"` + + JSON commonFieldsJSON +} + +type commonFieldsJSON struct { + Metadata Field + Value Field + ExtraFields map[string]Field + raw string +} + +type CardEmbedded struct { + CommonFields + Processor CardVisaProcessor `json:"processor"` + Data CardVisaData `json:"data"` + IsFoo bool `json:"is_foo"` + + JSON cardEmbeddedJSON +} + +type cardEmbeddedJSON struct { + Processor Field + Data Field + IsFoo Field + ExtraFields map[string]Field + raw string +} + +func (r cardEmbeddedJSON) RawJSON() string { return r.raw } + var portTests = map[string]struct { from any to any @@ -158,6 +191,52 @@ var portTests = map[string]struct { Value: false, }, }, + "embedded to card": { + CardEmbedded{ + CommonFields: CommonFields{ + Metadata: Metadata{ + CreatedAt: "Mar 29 2024", + }, + Value: "embedded_value", + JSON: commonFieldsJSON{ + Metadata: Field{raw: `{"created_at":"Mar 29 2024"}`, status: valid}, + Value: Field{raw: `"embedded_value"`, status: valid}, + raw: `should not matter`, + }, + }, + Processor: "visa", + IsFoo: true, + Data: CardVisaData{ + Foo: "embedded_foo", + }, + JSON: cardEmbeddedJSON{ + raw: `{"processor":"visa","is_foo":true,"data":{"foo":"embedded_foo"},"metadata":{"created_at":"Mar 29 2024"},"value":"embedded_value"}`, + Processor: Field{raw: `"visa"`, status: valid}, + IsFoo: Field{raw: `true`, status: valid}, + Data: Field{raw: `{"foo":"embedded_foo"}`, status: valid}, + }, + }, + Card{ + Processor: "visa", + IsFoo: true, + IsBar: false, + Data: CardVisaData{ + Foo: "embedded_foo", + }, + Metadata: Metadata{ + CreatedAt: "Mar 29 2024", + }, + Value: "embedded_value", + JSON: cardJSON{ + raw: `{"processor":"visa","is_foo":true,"data":{"foo":"embedded_foo"},"metadata":{"created_at":"Mar 29 2024"},"value":"embedded_value"}`, + Processor: Field{raw: `"visa"`, status: 0x3}, + IsFoo: Field{raw: "true", status: 0x3}, + Data: Field{raw: `{"foo":"embedded_foo"}`, status: 0x3}, + Metadata: Field{raw: `{"created_at":"Mar 29 2024"}`, status: 0x3}, + Value: Field{raw: `"embedded_value"`, status: 0x3}, + }, + }, + }, } func TestPort(t *testing.T) { diff --git a/internal/apijson/registry.go b/internal/apijson/registry.go index 2ea00ae..119cc5f 100644 --- a/internal/apijson/registry.go +++ b/internal/apijson/registry.go @@ -29,3 +29,13 @@ func RegisterUnion(typ reflect.Type, discriminator string, variants ...UnionVari unionVariants[variant.Type] = typ } } + +// Useful to wrap a union type to force it to use [apijson.UnmarshalJSON] since you cannot define an +// UnmarshalJSON function on the interface itself. +type UnionUnmarshaler[T any] struct { + Value T +} + +func (c *UnionUnmarshaler[T]) UnmarshalJSON(buf []byte) error { + return UnmarshalRoot(buf, &c.Value) +} diff --git a/internal/requestconfig/requestconfig.go b/internal/requestconfig/requestconfig.go index d925eab..3502bf5 100644 --- a/internal/requestconfig/requestconfig.go +++ b/internal/requestconfig/requestconfig.go @@ -138,6 +138,7 @@ func NewRequestConfig(ctx context.Context, method string, u string, body interfa req.Header.Set("Accept", "application/json") req.Header.Set("X-Stainless-Retry-Count", "0") + req.Header.Set("X-Stainless-Timeout", "0") for k, v := range getDefaultHeaders() { req.Header.Add(k, v) } @@ -157,6 +158,18 @@ func NewRequestConfig(ctx context.Context, method string, u string, body interfa if err != nil { return nil, err } + + // This must run after `cfg.Apply(...)` above in case the request timeout gets modified. We also only + // apply our own logic for it if it's still "0" from above. If it's not, then it was deleted or modified + // by the user and we should respect that. + if req.Header.Get("X-Stainless-Timeout") == "0" { + if cfg.RequestTimeout == time.Duration(0) { + req.Header.Del("X-Stainless-Timeout") + } else { + req.Header.Set("X-Stainless-Timeout", strconv.Itoa(int(cfg.RequestTimeout.Seconds()))) + } + } + return &cfg, nil } @@ -279,6 +292,41 @@ func parseRetryAfterHeader(resp *http.Response) (time.Duration, bool) { return 0, false } +// isBeforeContextDeadline reports whether the non-zero Time t is +// before ctx's deadline. If ctx does not have a deadline, it +// always reports true (the deadline is considered infinite). +func isBeforeContextDeadline(t time.Time, ctx context.Context) bool { + d, ok := ctx.Deadline() + if !ok { + return true + } + return t.Before(d) +} + +// bodyWithTimeout is an io.ReadCloser which can observe a context's cancel func +// to handle timeouts etc. It wraps an existing io.ReadCloser. +type bodyWithTimeout struct { + stop func() // stops the time.Timer waiting to cancel the request + rc io.ReadCloser +} + +func (b *bodyWithTimeout) Read(p []byte) (n int, err error) { + n, err = b.rc.Read(p) + if err == nil { + return n, nil + } + if err == io.EOF { + return n, err + } + return n, err +} + +func (b *bodyWithTimeout) Close() error { + err := b.rc.Close() + b.stop() + return err +} + 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. @@ -340,12 +388,17 @@ func (cfg *RequestConfig) Execute() (err error) { shouldSendRetryCount := cfg.Request.Header.Get("X-Stainless-Retry-Count") == "0" var res *http.Response + var cancel context.CancelFunc for retryCount := 0; retryCount <= cfg.MaxRetries; retryCount += 1 { ctx := cfg.Request.Context() - if cfg.RequestTimeout != time.Duration(0) { - var cancel context.CancelFunc + if cfg.RequestTimeout != time.Duration(0) && isBeforeContextDeadline(time.Now().Add(cfg.RequestTimeout), ctx) { ctx, cancel = context.WithTimeout(ctx, cfg.RequestTimeout) - defer cancel() + defer func() { + // The cancel function is nil if it was handed off to be handled in a different scope. + if cancel != nil { + cancel() + } + }() } req := cfg.Request.Clone(ctx) @@ -413,10 +466,15 @@ func (cfg *RequestConfig) Execute() (err error) { return &aerr } - if cfg.ResponseBodyInto == nil { - return nil - } - if _, ok := cfg.ResponseBodyInto.(**http.Response); ok { + _, intoCustomResponseBody := cfg.ResponseBodyInto.(**http.Response) + if cfg.ResponseBodyInto == nil || intoCustomResponseBody { + // We aren't reading the response body in this scope, but whoever is will need the + // cancel func from the context to observe request timeouts. + // Put the cancel function in the response body so it can be handled elsewhere. + if cancel != nil { + res.Body = &bodyWithTimeout{rc: res.Body, stop: cancel} + cancel = nil + } return nil } diff --git a/internal/version.go b/internal/version.go index 805f4ff..5f0be5b 100644 --- a/internal/version.go +++ b/internal/version.go @@ -2,4 +2,4 @@ package internal -const PackageVersion = "0.1.0-alpha.13" // x-release-please-version +const PackageVersion = "0.1.0-alpha.14" // x-release-please-version diff --git a/projectinferencepipeline.go b/projectinferencepipeline.go index 64e788f..a1f805c 100644 --- a/projectinferencepipeline.go +++ b/projectinferencepipeline.go @@ -338,22 +338,34 @@ func (r projectInferencePipelineNewResponseProjectGitRepoJSON) RawJSON() string } type ProjectInferencePipelineNewResponseWorkspace struct { - ID string `json:"id,required" format:"uuid"` - CreatorID string `json:"creatorId,required,nullable" format:"uuid"` - DateCreated time.Time `json:"dateCreated,required" format:"date-time"` - DateUpdated time.Time `json:"dateUpdated,required" format:"date-time"` - InviteCount int64 `json:"inviteCount,required"` - MemberCount int64 `json:"memberCount,required"` - Name string `json:"name,required"` - PeriodEndDate time.Time `json:"periodEndDate,required,nullable" format:"date-time"` - PeriodStartDate time.Time `json:"periodStartDate,required,nullable" format:"date-time"` - ProjectCount int64 `json:"projectCount,required"` - Slug string `json:"slug,required"` - Status ProjectInferencePipelineNewResponseWorkspaceStatus `json:"status,required"` - MonthlyUsage []ProjectInferencePipelineNewResponseWorkspaceMonthlyUsage `json:"monthlyUsage"` - SAMLOnlyAccess bool `json:"samlOnlyAccess"` - WildcardDomains []string `json:"wildcardDomains"` - JSON projectInferencePipelineNewResponseWorkspaceJSON `json:"-"` + // The workspace id. + ID string `json:"id,required" format:"uuid"` + // The workspace creator id. + CreatorID string `json:"creatorId,required,nullable" format:"uuid"` + // The workspace creation date. + DateCreated time.Time `json:"dateCreated,required" format:"date-time"` + // The workspace last updated date. + DateUpdated time.Time `json:"dateUpdated,required" format:"date-time"` + // The number of invites in the workspace. + InviteCount int64 `json:"inviteCount,required"` + // The number of members in the workspace. + MemberCount int64 `json:"memberCount,required"` + // The workspace name. + Name string `json:"name,required"` + // The end date of the current billing period. + PeriodEndDate time.Time `json:"periodEndDate,required,nullable" format:"date-time"` + // The start date of the current billing period. + PeriodStartDate time.Time `json:"periodStartDate,required,nullable" format:"date-time"` + // The number of projects in the workspace. + ProjectCount int64 `json:"projectCount,required"` + // The workspace slug. + Slug string `json:"slug,required"` + Status ProjectInferencePipelineNewResponseWorkspaceStatus `json:"status,required"` + MonthlyUsage []ProjectInferencePipelineNewResponseWorkspaceMonthlyUsage `json:"monthlyUsage"` + // Whether the workspace only allows SAML authentication. + SAMLOnlyAccess bool `json:"samlOnlyAccess"` + WildcardDomains []string `json:"wildcardDomains"` + JSON projectInferencePipelineNewResponseWorkspaceJSON `json:"-"` } // projectInferencePipelineNewResponseWorkspaceJSON contains the JSON metadata for @@ -733,22 +745,34 @@ func (r projectInferencePipelineListResponseItemsProjectGitRepoJSON) RawJSON() s } type ProjectInferencePipelineListResponseItemsWorkspace struct { - ID string `json:"id,required" format:"uuid"` - CreatorID string `json:"creatorId,required,nullable" format:"uuid"` - DateCreated time.Time `json:"dateCreated,required" format:"date-time"` - DateUpdated time.Time `json:"dateUpdated,required" format:"date-time"` - InviteCount int64 `json:"inviteCount,required"` - MemberCount int64 `json:"memberCount,required"` - Name string `json:"name,required"` - PeriodEndDate time.Time `json:"periodEndDate,required,nullable" format:"date-time"` - PeriodStartDate time.Time `json:"periodStartDate,required,nullable" format:"date-time"` - ProjectCount int64 `json:"projectCount,required"` - Slug string `json:"slug,required"` - Status ProjectInferencePipelineListResponseItemsWorkspaceStatus `json:"status,required"` - MonthlyUsage []ProjectInferencePipelineListResponseItemsWorkspaceMonthlyUsage `json:"monthlyUsage"` - SAMLOnlyAccess bool `json:"samlOnlyAccess"` - WildcardDomains []string `json:"wildcardDomains"` - JSON projectInferencePipelineListResponseItemsWorkspaceJSON `json:"-"` + // The workspace id. + ID string `json:"id,required" format:"uuid"` + // The workspace creator id. + CreatorID string `json:"creatorId,required,nullable" format:"uuid"` + // The workspace creation date. + DateCreated time.Time `json:"dateCreated,required" format:"date-time"` + // The workspace last updated date. + DateUpdated time.Time `json:"dateUpdated,required" format:"date-time"` + // The number of invites in the workspace. + InviteCount int64 `json:"inviteCount,required"` + // The number of members in the workspace. + MemberCount int64 `json:"memberCount,required"` + // The workspace name. + Name string `json:"name,required"` + // The end date of the current billing period. + PeriodEndDate time.Time `json:"periodEndDate,required,nullable" format:"date-time"` + // The start date of the current billing period. + PeriodStartDate time.Time `json:"periodStartDate,required,nullable" format:"date-time"` + // The number of projects in the workspace. + ProjectCount int64 `json:"projectCount,required"` + // The workspace slug. + Slug string `json:"slug,required"` + Status ProjectInferencePipelineListResponseItemsWorkspaceStatus `json:"status,required"` + MonthlyUsage []ProjectInferencePipelineListResponseItemsWorkspaceMonthlyUsage `json:"monthlyUsage"` + // Whether the workspace only allows SAML authentication. + SAMLOnlyAccess bool `json:"samlOnlyAccess"` + WildcardDomains []string `json:"wildcardDomains"` + JSON projectInferencePipelineListResponseItemsWorkspaceJSON `json:"-"` } // projectInferencePipelineListResponseItemsWorkspaceJSON contains the JSON @@ -910,9 +934,13 @@ func (r ProjectInferencePipelineNewParamsProjectGitRepo) MarshalJSON() (data []b } type ProjectInferencePipelineNewParamsWorkspace struct { - Name param.Field[string] `json:"name,required"` - Slug param.Field[string] `json:"slug,required"` - InviteCode param.Field[string] `json:"inviteCode"` + // The workspace name. + Name param.Field[string] `json:"name,required"` + // The workspace slug. + Slug param.Field[string] `json:"slug,required"` + // The workspace invite code. + InviteCode param.Field[string] `json:"inviteCode"` + // Whether the workspace only allows SAML authentication. SAMLOnlyAccess param.Field[bool] `json:"samlOnlyAccess"` WildcardDomains param.Field[[]string] `json:"wildcardDomains"` } diff --git a/usage_test.go b/usage_test.go index e535374..20d506e 100644 --- a/usage_test.go +++ b/usage_test.go @@ -38,9 +38,9 @@ func TestUsage(t *testing.T) { Rows: openlayer.F([]map[string]interface{}{{ "user_query": "what is the meaning of life?", "output": "42", - "tokens": map[string]interface{}{}, - "cost": map[string]interface{}{}, - "timestamp": map[string]interface{}{}, + "tokens": 7, + "cost": 0.02, + "timestamp": 1610000000, }}), }, )