diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ccdb53b..1112d0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,9 @@ jobs: run: go build -v ./... - name: Check generated code is up to date + env: + # Use the reviewed SDK schema while capture controls await API release. + NOTTE_API_URL: https://raw.githubusercontent.com/nottelabs/notte/a271724bc3ace49be3ec7d9c4617d87df1aa772d/openapi run: | go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.5.1 ./scripts/generate.sh diff --git a/internal/api/client.gen.go b/internal/api/client.gen.go index bbbb047..b82c1b2 100644 --- a/internal/api/client.gen.go +++ b/internal/api/client.gen.go @@ -1043,6 +1043,7 @@ const ( // Defines values for ReplayMissingResponseReplayStatus. const ( + ReplayMissingResponseReplayStatusDisabled ReplayMissingResponseReplayStatus = "disabled" ReplayMissingResponseReplayStatusFailed ReplayMissingResponseReplayStatus = "failed" ReplayMissingResponseReplayStatusProcessing ReplayMissingResponseReplayStatus = "processing" ReplayMissingResponseReplayStatusUnavailable ReplayMissingResponseReplayStatus = "unavailable" @@ -1166,10 +1167,10 @@ const ( // Defines values for YouTubeResultCaptionsStatus. const ( - YouTubeResultCaptionsStatusAvailable YouTubeResultCaptionsStatus = "available" - YouTubeResultCaptionsStatusDisabled YouTubeResultCaptionsStatus = "disabled" - YouTubeResultCaptionsStatusNotApplicable YouTubeResultCaptionsStatus = "not_applicable" - YouTubeResultCaptionsStatusUnavailable YouTubeResultCaptionsStatus = "unavailable" + Available YouTubeResultCaptionsStatus = "available" + Disabled YouTubeResultCaptionsStatus = "disabled" + NotApplicable YouTubeResultCaptionsStatus = "not_applicable" + Unavailable YouTubeResultCaptionsStatus = "unavailable" ) // Defines values for FunctionRuntimeHealthParamsSdkGeneration. @@ -1200,8 +1201,8 @@ const ( // Defines values for SessionStopParamsCloseReason. const ( - SessionStopParamsCloseReasonError SessionStopParamsCloseReason = "error" - SessionStopParamsCloseReasonManual SessionStopParamsCloseReason = "manual" + Error SessionStopParamsCloseReason = "error" + Manual SessionStopParamsCloseReason = "manual" ) // ActionParameter defines model for ActionParameter. @@ -1431,6 +1432,9 @@ type ApiSessionStartRequest struct { // IdleTimeoutMinutes Idle timeout in minutes. Session closes after this period of inactivity (resets on each operation). IdleTimeoutMinutes *int `json:"idle_timeout_minutes,omitempty"` + // LogSession Save browser session logs. False permanently disables saved logs, without disabling metering. + LogSession *bool `json:"log_session,omitempty"` + // MaxDurationMinutes Maximum session lifetime in minutes (absolute maximum, not affected by activity). MaxDurationMinutes *int `json:"max_duration_minutes,omitempty"` Profile *SessionProfile `json:"profile,omitempty"` @@ -1438,6 +1442,9 @@ type ApiSessionStartRequest struct { // Proxies List of custom proxies to use for the session. If True, the default proxies will be used. Proxies *ApiSessionStartRequest_Proxies `json:"proxies,omitempty"` + // RecordSession Save session replay. False permanently disables saved replay; Live View remains available. + RecordSession *bool `json:"record_session,omitempty"` + // ScreenshotType Ignored since notte 1.10: screenshots are returned without highlighting. // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set ScreenshotType *ApiSessionStartRequestScreenshotType `json:"screenshot_type,omitempty"` @@ -4832,6 +4839,7 @@ type SessionResponse struct { // LastAccessedAt Last access time LastAccessedAt FlexibleTime `json:"last_accessed_at"` + LogSession *bool `json:"log_session,omitempty"` // MaxDurationMinutes Session max duration in minutes. Will timeout if now() > creation time + max_duration_minutes MaxDurationMinutes *int `json:"max_duration_minutes,omitempty"` @@ -4843,7 +4851,8 @@ type SessionResponse struct { NetworkResponseBytes *int `json:"network_response_bytes,omitempty"` // Proxies Whether proxies were used for the session. True if any proxy was applied during session creation. - Proxies *bool `json:"proxies,omitempty"` + Proxies *bool `json:"proxies,omitempty"` + RecordSession *bool `json:"record_session,omitempty"` // SessionId The ID of the session (created or existing). Use this ID to interact with the session for the next operation. SessionId string `json:"session_id"` @@ -7035,6 +7044,14 @@ func (a *SessionResponse) UnmarshalJSON(b []byte) error { delete(object, "last_accessed_at") } + if raw, found := object["log_session"]; found { + err = json.Unmarshal(raw, &a.LogSession) + if err != nil { + return fmt.Errorf("error reading 'log_session': %w", err) + } + delete(object, "log_session") + } + if raw, found := object["max_duration_minutes"]; found { err = json.Unmarshal(raw, &a.MaxDurationMinutes) if err != nil { @@ -7067,6 +7084,14 @@ func (a *SessionResponse) UnmarshalJSON(b []byte) error { delete(object, "proxies") } + if raw, found := object["record_session"]; found { + err = json.Unmarshal(raw, &a.RecordSession) + if err != nil { + return fmt.Errorf("error reading 'record_session': %w", err) + } + delete(object, "record_session") + } + if raw, found := object["session_id"]; found { err = json.Unmarshal(raw, &a.SessionId) if err != nil { @@ -7238,6 +7263,13 @@ func (a SessionResponse) MarshalJSON() ([]byte, error) { return nil, fmt.Errorf("error marshaling 'last_accessed_at': %w", err) } + if a.LogSession != nil { + object["log_session"], err = json.Marshal(a.LogSession) + if err != nil { + return nil, fmt.Errorf("error marshaling 'log_session': %w", err) + } + } + if a.MaxDurationMinutes != nil { object["max_duration_minutes"], err = json.Marshal(a.MaxDurationMinutes) if err != nil { @@ -7266,6 +7298,13 @@ func (a SessionResponse) MarshalJSON() ([]byte, error) { } } + if a.RecordSession != nil { + object["record_session"], err = json.Marshal(a.RecordSession) + if err != nil { + return nil, fmt.Errorf("error marshaling 'record_session': %w", err) + } + } + object["session_id"], err = json.Marshal(a.SessionId) if err != nil { return nil, fmt.Errorf("error marshaling 'session_id': %w", err) diff --git a/internal/cmd/sessionstart_flags.gen.go b/internal/cmd/sessionstart_flags.gen.go index 9558663..4828077 100644 --- a/internal/cmd/sessionstart_flags.gen.go +++ b/internal/cmd/sessionstart_flags.gen.go @@ -40,6 +40,9 @@ var ( // Idle timeout in minutes. Session closes after this period of inactivity (resets on each operation). SessionStartIdleTimeoutMinutes int + // Save browser session logs. False permanently disables saved logs, without disabling metering. + SessionStartLogSession bool + // Maximum session lifetime in minutes (absolute maximum, not affected by activity). SessionStartMaxDurationMinutes int @@ -47,6 +50,9 @@ var ( SessionStartProfileId string SessionStartProfilePersist bool + // Save session replay. False permanently disables saved replay; Live View remains available. + SessionStartRecordSession bool + // Ignored since notte 1.10: screenshots are returned without highlighting. SessionStartScreenshotType string @@ -84,10 +90,12 @@ func RegisterSessionStartFlags(cmd *cobra.Command) { cmd.Flags().StringSliceVar(&SessionStartChromeArgs, "chrome-args", []string{}, "Overwrite the chrome instance arguments (repeatable)") cmd.Flags().BoolVar(&SessionStartDemonstrate, "demonstrate", false, "Whether to enable the Notte recorder extension for this session. The extension is installed but remains inactive when this is false. (API default: false)") cmd.Flags().IntVar(&SessionStartIdleTimeoutMinutes, "idle-timeout-minutes", 0, "Idle timeout in minutes. Session closes after this period of inactivity (resets on each operation). (API default: 3)") + cmd.Flags().BoolVar(&SessionStartLogSession, "log-session", false, "Save browser session logs. False permanently disables saved logs, without disabling metering. (API default: true)") cmd.Flags().IntVar(&SessionStartMaxDurationMinutes, "max-duration-minutes", 0, "Maximum session lifetime in minutes (absolute maximum, not affected by activity). (API default: 15)") // profile (flattened object) cmd.Flags().StringVar(&SessionStartProfileId, "profile-id", "", "Profile ID to use for this session") cmd.Flags().BoolVar(&SessionStartProfilePersist, "profile-persist", false, "Whether to save browser state to profile on session close (API default: false)") + cmd.Flags().BoolVar(&SessionStartRecordSession, "record-session", false, "Save session replay. False permanently disables saved replay; Live View remains available. (API default: true)") cmd.Flags().StringVar(&SessionStartScreenshotType, "screenshot-type", "", "Ignored since notte 1.10: screenshots are returned without highlighting. (API default: last_action) (raw, full, last_action)") cmd.Flags().BoolVar(&SessionStartSolveCaptchas, "solve-captchas", false, "Whether to try to automatically solve captchas (API default: true)") cmd.Flags().StringVar(&SessionStartUserAgent, "user-agent", "", "The user agent to use for the session") @@ -143,6 +151,10 @@ func BuildSessionStartRequest(cmd *cobra.Command) (*api.ApiSessionStartRequest, body.IdleTimeoutMinutes = &SessionStartIdleTimeoutMinutes } + if cmd.Flags().Changed("log-session") { + body.LogSession = &SessionStartLogSession + } + if SessionStartMaxDurationMinutes > 0 { body.MaxDurationMinutes = &SessionStartMaxDurationMinutes } @@ -163,6 +175,10 @@ func BuildSessionStartRequest(cmd *cobra.Command) (*api.ApiSessionStartRequest, body.Profile = &profile } + if cmd.Flags().Changed("record-session") { + body.RecordSession = &SessionStartRecordSession + } + if SessionStartScreenshotType != "" { val := api.ApiSessionStartRequestScreenshotType(SessionStartScreenshotType) body.ScreenshotType = &val diff --git a/internal/cmd/sessionstart_flags_test.go b/internal/cmd/sessionstart_flags_test.go index c10307e..17691c7 100644 --- a/internal/cmd/sessionstart_flags_test.go +++ b/internal/cmd/sessionstart_flags_test.go @@ -93,3 +93,42 @@ func TestAdvancedStealthMapping(t *testing.T) { }) } } + +func TestCaptureFlagsPreserveFalseAndOmission(t *testing.T) { + for _, flag := range []string{"record-session", "log-session"} { + for _, value := range []string{"", "true", "false"} { + t.Run(flag+"/"+value, func(t *testing.T) { + cmd := newSessionStartFlagsCmd() + if value != "" { + cmd.SetArgs([]string{"--" + flag + "=" + value}) + } + if err := cmd.Execute(); err != nil { + t.Fatal(err) + } + body, err := BuildSessionStartRequest(cmd) + if err != nil { + t.Fatal(err) + } + data, err := json.Marshal(body) + if err != nil { + t.Fatal(err) + } + var payload map[string]any + if err := json.Unmarshal(data, &payload); err != nil { + t.Fatal(err) + } + field := "record_session" + if flag == "log-session" { + field = "log_session" + } + got, present := payload[field] + if present != (value != "") { + t.Fatalf("%s presence=%v", field, present) + } + if present && got != (value == "true") { + t.Fatalf("%s = %v", field, got) + } + }) + } + } +}